深刻了解JVM - 实战JVM工具(下)

前言

接着上篇持续讲述,上一篇模仿了两个还算比拟相熟的场景,剖析了之前老年代优化是如何解决的,以及应用jstat剖析工具如何剖析出JVM的问题,这一节会持续扩大,将会列举更多的案例来分析线上的JVM问题。

前文回顾

上一节通过一个APP的JVM内存剖析解释了一些比拟非凡的参数如何影响JVM,以及剖析了之前老年代优化的文章中对于jstat如何进行剖析和优化。

概述:

  1. 介绍三个JVM调优的案例,一步一步剖析问题和解决办法。
  2. 总结剖析思路和解决流程,自我思考和反思。
  3. 总结和集体感想。

案例实战

案例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频繁的点:

  1. 能够看到小机器给JVM的堆内存没有多少空间,线程和办法去要分进来一点,JVM的资源非常吃紧。
  2. 新生代显著太小了,而且ratio设置为5,导致最初EDEN区只有可怜的300M左右的空间
  3. 68%的CMS老年代回收阈值仿佛有点小,齐全能够改为92%才执行回收,老年代的空间比拟大
  4. -XX:+CMSParallelRemarkEnabled-XX:+UseCMSInitiatingOccupancyOnly这两个参数的作用请自行百度。

问题剖析

  1. 在后续的排查发现每隔十几分钟就会呈现大量的 大对象间接进入老年代,大对象的产生起因是因为开发人员应用的“全表查问”导致了几十万的数据被查出来,这里能够应用jmap的工具进行排查发现生成一个很大的ArrayList,并且外部都是同一个对象。
  2. 尽管新生代回收之后对象很少的对象进入老年代,几十M,然而能够发现动静规定的判断之后,survior还是有几十M的对象进入到了老年代的空间。
  3. 新生代的空间很容易丰满,老年代预留空间较大。
  4. CMS的阈值设置为68,则达到老年代的68就开始回收,有点过于激进

解决办法:

  1. 如果有条件还是须要加机器,因为机器的性能的确受限。(2G我开IDEA都够呛)
  2. 新生代显著太小了,所以扩充到1G的空间很有必要,同时还是依照5:1:1的调配计划,给survior区域足够的空间和大小。
  3. CMS的回收阈值设置到92%。不须要太过激进。
  4. 办法区指定一个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分钟就可以看完:

业务场景:

  1. 一秒一次FULL GC,每次都须要几百毫秒
  2. 平时流量拜访不大的时候新生代对象增长不快,老年代占用不到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

第二个答复

办法拜访器和结构器拜访器要么是本机的,要么是生成的。这意味着咱们对办法应用 NativeMethodAccessorImplGeneratedMethodAccessor,对构造函数应用 NativeConstructorAccessorImplGeneratedConstructorAccessor。拜访器能够是原生的或生成的,并由两个零碎属性管制和决定:

sun.reflect.noInflation = false(默认值为 false)sun.reflect.inflationThreshold = 15(默认值为 15)

sun.reflect.noInflation 设置为 true 时,将始终生成所应用的拜访器,零碎属性 sun.reflect.inflationThreshold 没有任何意义。当 sun.reflect.noInflationfalse 并且 sun.reflect.inflationThreshold 设置为 15 时(如果未指定,这是默认行为)那么这意味着对于构造函数(或办法)的前 15 次访问,本机生成器将被应用,尔后将提供一个生成的拜访器(来自 ReflectionFactory)以供应用。

Native 拜访器应用本地调用来访问信息,而生成的拜访器都是字节码,因而速度十分快。另一方面,生成的拜访器须要工夫来实例化和加载(基本上是收缩,因而管制它的零碎属性的名称包含“收缩”一词)。

更多细节能够在原始博客中找到.....