共计 5849 个字符,预计需要花费 15 分钟才能阅读完成。
深刻了解 JVM – 实战 JVM 工具(下)
前言
接着上篇持续讲述,上一篇模仿了两个还算比拟相熟的场景,剖析了之前老年代优化是如何解决的,以及应用 jstat 剖析工具如何剖析出 JVM 的问题,这一节会持续扩大,将会列举更多的案例来分析线上的 JVM 问题。
前文回顾
上一节通过一个 APP 的 JVM 内存剖析解释了一些比拟非凡的参数如何影响 JVM,以及剖析了之前老年代优化的文章中对于 jstat 如何进行剖析和优化。
概述:
- 介绍三个 JVM 调优的案例,一步一步剖析问题和解决办法。
- 总结剖析思路和解决流程,自我思考和反思。
- 总结和集体感想。
案例实战
案例 1:办法区一直解体如何排查?
业务场景:
之前的案例根本都是和堆扯上关系,这个案例比拟非凡是由 JVM 参数设置谬误 引起的频繁的卡顿问题,简略来说就是设置了参数之后就呈现拜访零碎卡顿并且线上一直的进行 FULL GC 的报警,这里先不阐明改了哪一个参数,而是先来剖析一下:
- 发现零碎在十分钟内进行了 3 次 FULL GC,这个频繁非常高
- 通过线上的 JSTAT 排查发现呈现了报错
Methoddata GC Threashold
等字样
依据第二点咱们初步判定是 JVM 的办法区溢出了,为什么 JVM 的办法区溢出会触发 FULL GC?事实上的确如此,因为通常状况下 FULL GC 也会带动办法区的回收。这一块的材料网上能够搜到一大堆,这里不再具体的介绍。
问题剖析:
退出验证参数剖析:
既然是办法区的问题,为了进一步的排查这个办法区溢出的问题,这里须要退出上面的两个参数:
-XX:TraceClassLoading
-XX:TraceClassUnloading
这两个参数的作用是追踪 类加载 和类卸载 的状况,在加上这两个参数之后持续剖析,之后发现在日志的文件当中发现了上面的内容:
显著能够看到 JVM 一直的加载了一个叫做 GeneratedSerializationCOnstructorAccessor
的类,就是这个类一直的加载导致了 metaspace
区域占满,这也导致了metaspace 的对象太多触发 FULL GC,所以罪魁祸首就是这个奇怪的类GeneratedSerializationCOnstructorAccessor
。
为什么会呈现奇怪的类?
这里应用 google 搜寻看看这个类是什么,查问后果发现这是 JDK 内置类,通过查阅材料咱们能够晓得这是由 反射 生成的类。
反射的知识点也不再补充,能够了解为一种通过 JVM 的类加载器联合 JVM 的工具包生成建设对象的一种形式,也是许多框架的灵魂 ss。
其实通过材料查阅咱们还能够发现 反射须要 JVM 动静的生产一些下面所说的奇怪的类到 MetaSpace 区域 ,比方要生成动静类ProxyClass@Proxy123123
等等相似这种对象(反射生产的类标识比拟非凡),JDK 都须要下面的辅助对象进行操作。
这里咱们还须要在理解一个概念,就是 反射生成的类都是应用的软援用!至于这个软援用在这里产生了什么影响,这里也先卖个关子,到下文联合把零碎搞解体的参数一起进行剖析。
什么是软援用?在内存空间有余的时候被强制回收,不论是否存在局部变量援用。
接着剖析对于软援用的存活工夫,jvm 应用了上面的公式来计算这个软援用的生命周期:
这个公式的含意:示意一个软援用有多久没有被拜访过了,freespace
代表了以后的 JVM 中的闲暇内存,softref
代表每一个 MB 的空间内存空间能够容许SoftReference
存活多久。
估算值:假如当初空间有 3000M 的对象,softrefLRUpolicyMSPerMB
的值为 1000 毫秒,意味着这些对象会存活 3000 秒 也就是 50 分钟左右。
以上就是奇怪的类呈现的起因,是因为反射惹的祸。
排查后果:
到底设置了什么参数?
这里讲一下到底设置了参数,让反射一直生成对象把办法区占满了,这里设置的参数如下:
-XX:SoftRefLRUPolicyMSPerMB=0
这个参数。后果 JVM 就翻车了。
为什么呈现奇怪的对象越来越多?
咱们再看一下下面的公式:
假如咱们不小心把这个值设置为 0 有什么结果呢?
当然是会导致下面 clock 公式的计算结果为 0 的,后果就是 JVM 发现每次反射调配的对象马上就会被回收掉,而后接着又会通过代理生成代理对象,简略来说这个参数 导致每次 soft 软援用的对象一旦调配就会马上被回收.
再次强调一下 反射机制导致动静代理类一直的被新增,然而这部分对象又被马上回收掉,导致办法区的垃圾对象越来越多 这就是为什么奇怪的对象越来越多的起因。
为什么会想着设置这个参数?
设置这个参数的起因也很天真:为了让反射生成的代理对象能够尽快被垃圾回收,如果设置为为 0,当办法区的内存占用能够小一些,并且也能够及时回收,而后后果就是 善意办好事。
解决办法:
这里解决办法很简略,就是设置一个大于 0 的值并且最好是 1000、2000、5000 这一类数字,就是不能设置的过小或者设置为 0,否则会导致办法区一直的占用后果办法去溢出最终又导致FULL GC。
总结:
这个案例可能看下面的阐明很简略就解决了,然而实际上真正碰到相似问题,必定会呈现各种摸不着头脑的状况,心愿这篇案例能够让读者设置 JVM 参数的时候都要验证一下这个参数的影响以及肯定要确认他的参数和实际效果是统一的!
这个问题也齐全是人的问题,退出没有好奇去想当然的设置一个奇怪的参数,也不至于造成各种奇怪的问题。
最初,只有多学习理论案例,平时多看看他人是如何排查问题的,这对本人的晋升也有很大帮忙。
案例 2:每天数十次 GC 的线上零碎怎么解决?
业务场景:
这个案例和上一个一样是一个理论的案例,话不多说,间接得出过后没优化过的零碎的 JVM 性能体现大抵如下:
- 机器配置:2 核 4G
- JVM 堆内存大小:2G
- 零碎运行工夫:6 天
- 零碎运行 6 天内产生的 Full GC 次数和耗时:250 次,70 多秒
- 零碎运行 6 天内产生的 Young GC 次数和耗时:2.6 万次,1400 秒
在应用的时候会发现问题如下:
- 每天会产生 40 屡次 Full GC,均匀每小时 2 次,每次
Full GC
在 300 毫秒左右 - 每天会产生 4000 屡次 YGC,每分钟 3 次,每次 YGC 在 50 秒左右。
介绍到这里也能够发现这个零碎的性能相当之差,每 2 小时就会 Full GC。这是必须要进行优化的。
优化前 JVM 参数:
上面是系统优化之前设置的参数:
-Xms1536M -Xmx1536M -Xmn512M -Xss256K -XX:SurvivorRatio=5 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=68 -XX:+CMSParallelRemarkEnabled -XX:+UseCMSInitiatingOccupancyOnly -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC
咱们先不察看其余内容,单从参数自身看下可能会让 GC 频繁的点:
- 能够看到小机器给 JVM 的堆内存没有多少空间,线程和办法去要分进来一点,JVM 的资源非常吃紧。
- 新生代显著太小了,而且 ratio 设置为 5,导致最初 EDEN 区只有可怜的 300M 左右的空间
- 68% 的 CMS 老年代回收阈值仿佛有点小,齐全能够改为 92% 才执行回收,老年代的空间比拟大
-XX:+CMSParallelRemarkEnabled
和-XX:+UseCMSInitiatingOccupancyOnly
这两个参数的作用请自行百度。
问题剖析
- 在后续的排查发现每隔十几分钟就会呈现大量的 大对象 间接进入老年代,大对象的产生起因是因为开发人员应用的“全表查问”导致了几十万的数据被查出来,这里能够应用 jmap 的工具进行排查发现生成一个很大的 ArrayList,并且外部都是同一个对象。
- 尽管新生代回收之后对象很少的对象进入老年代,几十 M,然而能够发现动静规定的判断之后,survior 还是有几十 M 的对象进入到了老年代的空间。
- 新生代的空间很容易丰满,老年代预留空间较大。
- CMS 的阈值设置为 68,则达到老年代的 68 就开始回收,有点过于激进
解决办法:
- 如果有条件还是须要加机器,因为机器的性能的确受限。(2G 我开 IDEA 都够呛)
- 新生代显著太小了,所以扩充到 1G 的空间很有必要,同时还是依照 5:1:1 的调配计划,给 survior 区域足够的空间和大小。
- CMS 的回收阈值设置到 92%。不须要太过激进。
- 办法区指定一个 256M 的大小,如果不设置任何参数 默认的办法区大小只有 64M 左右的空间。
优化之后的参数:
上面是系统优化之后的参数:
-Xms1536M -Xmx1536M -Xmn1024M -Xss256K -XX:SurvivorRatio=5 -XX:PermSize=256M -XX:MaxPermSize=256M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=92 -XX:+CMSParallelRemarkEnabled -XX:+UseCMSInitiatingOccupancyOnly -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC
这里再次强调一下上文提到说须要百度的参数:
-XX:+UseCMSInitiatingOccupancyOnly -XX:+CMSParallelRemarkEnabled 这两个参数,这些参数有什么用?
第一个参数:示意每次都在 CMS 达到设置指标的状况下进行垃圾回收,不会因为 JVM 动静的判断导致提前进行 FULL GC
(这个参数有必要阐明一下,因为之前的文章提到过 JDK6 之后 CMS 的默认 92% 的配比,其实这个配比在理论运行的时候 会依据 CMS 老年代的回收状况提前或者提早回收,只能说 JVM 细节切实是有点多,只有记住开启这个参数之后,CMS 会固定真的到达到 92% 这个比例才进行 Full GC 垃圾回收的动作)
第二个参数:示意进行 并发标记 的步骤之前,先进行一次 YGC,其实实践上来说通常都应该进行一次,然而实际上如果不配置 JVM 会依据理论状况决定是否进行 YGC,起因也比较复杂,有趣味能够把参数复制之后百度补一下课。
案例 3:重大的 FULL GC 导致卡死?
最初介绍一个简略的案例,真的非常简略,3 分钟就可以看完:
业务场景:
- 一秒一次 FULL GC,每次都须要几百毫秒
- 平时流量拜访不大的时候新生代对象增长不快,老年代占用不到 10%,办法区也就 20% 应用。然而一旦到了顶峰期间就是频繁的 FULL GC
剖析:
在频繁 FULL GC 的工夫点进行 GC 日志剖析,同时应用 JMAP 剖析发现在顶峰期间呈现大批次操作的对象,这个对象基于一个报表的批量解决的操作,会产生大量的对象并且马上登程回收,后果发现 JVM 内存放不下导致频繁的 FULL GC。
这就很奇怪了咱们都晓得即便是在平时状况下即便很大数据的批量解决少数状况下并没有离谱到一秒一次 FULL GC,那么呈现这个问题毫无疑问就是代码的问题了。
通过排查发现,竟然有开发人员手动调用垃圾回收也就是 System.gc()
。这是一个臭名远扬的办法,具体的解释能够看看《Effective Java》 中的 第八条 : 防止应用终结办法和革除办法.
解决办法:
为了避免System.gc()
失效,这里应用上面的参数禁止掉:
-XX:+DisableExplictGC
总结:
不要写System.gc()
,最好是齐全不要晓得有这个办法。去探索起因其实也是比拟浪费时间的事件。
写在最初
JVM 的工具实战的高低两篇文章到这里就完结了,后续的文章仍然会是实战的局部,从这几个案例能够看到更多的状况下并不是 JVM 的问题,而是人的问题。
所以 写出优质好了解的代码是本分,写出性能好的代码是程度的体现。先写出好代码能力防止线上呈现各种莫名其妙的问题难以排查,而把握线上的排查和思考伎俩,能够让集体的能力失去无效的锤炼,所以多多试验和尝试是这篇文章的意义
参考资料:
GeneratedSerializationConstructorAccessor
的材料
材料 1:How the sun.reflect.GeneratedSerializationConstructorAccessor class generated 具备非凡上网姿态的倡议浏览
答案来自 https://stackoverflow.com/que…
上面摘自答案的机翻:
第一个答复:
这是因为(可能是您在应用程序中应用反射)堆空间有余,GC 试图通过卸载未应用的对象来开释一些内存,这就是为什么您会看到 Unloading class
sun.reflect.GeneratedSerializationConstructorAccessor
第二个答复
办法拜访器和结构器拜访器要么是本机的,要么是生成的。这意味着咱们对办法应用 NativeMethodAccessorImpl
或 GeneratedMethodAccessor
,对构造函数应用 NativeConstructorAccessorImpl
和 GeneratedConstructorAccessor
。拜访器能够是原生的或生成的,并由两个零碎属性管制和决定:
sun.reflect.noInflation = false(默认值为 false)sun.reflect.inflationThreshold = 15(默认值为 15)
当 sun.reflect.noInflation
设置为 true 时,将始终生成所应用的拜访器,零碎属性 sun.reflect.inflationThreshold
没有任何意义。当 sun.reflect.noInflation
为 false 并且 sun.reflect.inflationThreshold
设置为 15 时(如果未指定,这是默认行为)那么这意味着对于构造函数(或办法)的前 15 次访问,本机生成器将被应用,尔后将提供一个生成的拜访器(来自 ReflectionFactory)以供应用。
Native 拜访器应用本地调用来访问信息,而生成的拜访器都是字节码,因而速度十分快。另一方面,生成的拜访器须要工夫来实例化和加载(基本上是收缩,因而管制它的零碎属性的名称包含“收缩”一词)。
更多细节能够在原始博客中找到 …..