上篇文章中附加了一个最近碰到的奇怪的case,有位同学看到这个后周末时间折腾了下,把原因给分析出来了,分析过程很赞(无论是思路,还是工具的使用),非常感谢这位同学(阿里的一位同事,花名叫彦贝),在征求他的同意后,我把他写的整个问题的分析文章转载到这里。

文里图比较多,手机上不方便看的话,建议大家电脑上直接访问http://hellojava.info看。

上分析工具VisualVM

在解决很多问题的时候,工具起的作用往往是巨大的,很多时候通过工具分析,很快便能找到原因,但是这次并没有,下图是VisualVM观察到Heap上的GC图表。

从图表中可以看出,Perm区空间基本水平,但是Old区空间成增长态势与YGCT时间增长的倍率基本一致。熟悉YGC的朋友都知道YGC主要分为标记和回收两个阶段,YGCT也是基于这2个阶段统计的。由于每次回收的空间大小差不多,所以怀疑是标记阶段使用的时间比较长,下面回顾一下JVM的垃圾标记方式。

JVM垃圾回收的标记方法-枚举根节点

在Java语言里面,可作为GC Roots的节点主要在全局性的引用(例如常量或类静态属性)与执行上下文(例如栈帧中的本地变量表)中。如果要使用可达性分析来判断内存是否可回收,那分析工作必须在一个能保障一致性的快照中进行——这里“一致性”的意思是整个分析期间整个执行系统看起来就像被冻结在某个时间点上,不可以出现分析过程中,对象引用关系还在不断变化的情况,这点不满足的话分析结果准确性就无法保证。这点也是导致GC进行时必须“Stop The World”的其中一个重要原因,即使是号称(几乎)不会发生停顿的CMS收集器中,枚举根节点时也是必须要停顿的。

由于目前的主流JVM使用的都是准确式GC,所以当执行系统停顿下来之后,并不需要一个不漏地检查完所有执行上下文和全局的引用位置,虚拟机应当是有办法直接得到哪些地方存放着对象引用。在HotSpot的实现中,是使用一组称为OopMap的数据结构来达到这个目的,在类加载完成的时候,HotSpot就把对象内什么偏移量上是什么类型的数据计算出来,在JIT编译过程中,也会在特定的位置记录下栈里和寄存器里哪些位置是引用。这样GC在扫描时就就可以直接得知这些信息了。

上面这段引用自《深入理解Java虚拟机》,用个图简单表示一下,当然图也是源于书中:

基于对GC Roots的怀疑,猜测Old区中存在越来越多的gc root节点,那什么样的对象是root节点呢?不懂的我赶紧google了一下。

(不要看我红色圈出来了,第一次看到这几个嫌疑犯时我也拿不准是哪个)

为了进一步验证是Old区的GC Roots造成YGCT 增加的,我们来做一次full gc,干掉Old区。代码如下:

public class SlowYGC {public static void main(String[] args) throws Exception {int i= 0;while (true) {XStream xs = new XStream();xs.toString();xs = null;if(i++ % 10000 == 0){System.gc();}}}
}

可以看出Full GC后 YGCT锐减到初始状态。那是Full GC到底回收了哪些对象?进入到下一步,增加VM参数。

增加VM参数

为了看到Full GC前后对象的回收情况,我们增加下面2个VM参数

-XX:+PrintClassHistogramBeforeFullGC

-XX:+PrintClassHistogramAfterFullGC。

重新运行上面的代码,可以观察到下面的日志:

上图左边是FGC前的对象情况,右边是FGC后的情况,结合我之前给出的GC Root候选人中的Monitor锁,相信你很快就找到20026个[Ljava.util.concurrent.ConcurrentHashMap$Segment对象几乎被全部回收了,ConcurrentHashMap中正是通过Segment来实现分段锁的。那什么逻辑会导致出现大量的Segment锁对象。继续看Full GC日志com.thoughtworks.xstream.core.util.CompositeClassLoader这个对象也差不多在FGC的时候全军覆没,所以怀疑是ClassLoader引起的锁竞争,下面在ClassLoader的源码中继续查找。

ClassLoader中的ConcurrentHashMap

这里有个拿锁的动作,跟进去看看呗。

为了验证走到这块逻辑下了一个断点。剩下的就是putIfAbsent方法(这里就不详细分析实现了)有兴趣的同学可以看看源码中CAS和tryLock的使用。

至此基本分析和定位出了YGCT原因,在去看看Xstream的构造函数。

可以看出每次new XstreamI()的同时会new一个新的ClassLoader,基本上证明了上述怀疑和猜测。

推导一下按照上述分析,应该是所有自定义的ClassLoader都会YGCT变长的时间问题,于是手写一个ClassLoader验证一下。Java代码如下:

public class TestClassLoader4YGCT {public static void main(String[] args) throws Exception{while(true){Object obj = newObject();obj.toString();obj = null;}}private static Object newObject() throws Exception{ClassLoader classLoader = new ClassLoader() {@Overridepublic Class<?> loadClass(String s) throws ClassNotFoundException {try{String fileName = s.substring(s.lastIndexOf(".") + 1)+ ".class";InputStream inputStream = getClass().getResourceAsStream(fileName);if(inputStream == null){return super.loadClass(s);}byte[] b = new byte[inputStream.available()];inputStream.read(b);return defineClass(s,b,0,b.length);}catch (Exception e){System.out.println(e);return null;}}};Class<?> obj = classLoader.loadClass("jvmstudy.classload.TestClassLoader4YGCT");return obj.newInstance();}
}

VM 参数

-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xms512m -Xmx512m -Xmn100m -XX:+UseConcMarkSweepGC

GC日志

结论

当大量new自定义的ClassLoader来加载时,会产生大量ClassLoader对象以及parallelLockMap(ConcurrentHashMap)对象,导致产生大量的Segment分段锁对象,大大增加了GC Roots的数量,导致YGC中的标记时间变长。如果能直接看到YGCT的详细使用情况,这个结论会显得更加严谨。这只是我自己的一个推导,并不一定是正确答案。

更多深入分析

深入JVM彻底剖析前面ygc越来越慢的case

上篇文章中ygc越来越慢的case的原因解读相关推荐

  1. 深入JVM彻底剖析前面ygc越来越慢的case

    阿里JVM团队的同学帮助从JVM层面继续深入的剖析了下前面那个ygc越来越慢的case,分析文章相当的赞,思路清晰,工具熟练,JVM代码熟练,请看这位同学(阿里JVM团队:寒泉子)写的文章,我转载到这 ...

  2. mye连接mysql数据库_MySQL_如何在Java程序中访问mysql数据库中的数据并进行简单的操作,在上篇文章给大家介绍了Myeclip - phpStudy...

    如何在Java程序中访问mysql数据库中的数据并进行简单的操作 在上篇文章给大家介绍了Myeclipse连接mysql数据库的方法,通过本文给大家介绍如何在Java程序中访问mysql数据库中的数据 ...

  3. 免插件为WordPress文章中标签添加内链

    给文章标签添加内链,意思就是说,如果你文章中出现了和标签一样的文字,那么这个文字就会自动成为标签链接,你点击这个链接就会查看到所有含有该标签的文章,这个能方便用户浏览,据说还利于SEO.下面说说方法: ...

  4. android拍照截图组件,Android_Android实现从相册截图的功能,在这篇文章中,我将向大家展 - phpStudy...

    Android实现从相册截图的功能 在这篇文章中,我将向大家展示如何从相册截图. 先看看效果图: 上一篇文章中,我就拍照截图这一需求进行了详细的分析,试图让大家了解Android本身的限制,以及我们应 ...

  5. 关于JVM中YGC的来龙去脉

    本文来说下关于JVM中YGC的来龙去脉 文章目录 概述 查找GC Roots 遍历活跃对象 本文小结 概述 一次YGC过程主要分成两个步骤: 查找GC Roots,拷贝所引用的对象到 to 区: 递归 ...

  6. php 字母数字混合排序,JavaScript_基于JS实现数字+字母+中文的混合排序方法,在上篇文章给大家介绍了JavaScr - phpStudy...

    基于JS实现数字+字母+中文的混合排序方法 在上篇文章给大家介绍了JavaScript sort数组排序方法和自我实现排序方法小结,用自己的方法实现了数字数组的排序. 当然,实际运用中,我还是会使用s ...

  7. FigDraw 20. SCI文章中绘图之马赛克图 (mosaic)

    点击关注,桓峰基因 桓峰基因公众号推出基于R语言绘图教程并配有视频在线教程,目前整理出来的教程目录如下: FigDraw 1. SCI 文章的灵魂 之 简约优雅的图表配色 FigDraw 2. SCI ...

  8. php数据group去重,MongoDB_Mongodb聚合函数count、distinct、group如何实现数据聚合操作, 上篇文章给大家介绍了Mong - phpStudy...

    Mongodb聚合函数count.distinct.group如何实现数据聚合操作 上篇文章给大家介绍了Mongodb中MapReduce实现数据聚合方法详解,我们提到过Mongodb中进行数据聚合操 ...

  9. 微信公众号文章中如何添加及上传pdf、doc、xls等文件给粉丝下载

    在这个信息化的时代,越来越多的人都开始利用网络社交软件获取或者传播资讯 在众多的社交软件中,微信绝对是最大的社交平台 与日俱增的用户群,是政府.企事业单位与用户沟通.信息交流的优质平台 因此越来越多的 ...

最新文章

  1. python接口自动化5-Json数据处理
  2. PRAGMA EXCEPTION_INIT
  3. 暗影精灵5学计算机够用吗,为什么说暗影精灵5值得买?拆客给你看本!
  4. python在线考试系统设计csdn下载_一种通用的网页相似度检测算法
  5. 打造集成SATA驱动程序的XP系统盘
  6. oracle数据库使用
  7. ATP-EMTP电缆LCC模型中相数与电缆数的设置
  8. 「第五章」点击劫持(ClickJacking)
  9. 锁升级过程(偏向锁/轻量级锁/重量级锁)
  10. 联想小新 青春版-14笔记本电脑重装系统教程
  11. win10查看端口号
  12. Windows下的定时任务设置
  13. 餐饮连锁店远程视频监控系统设计需求分析
  14. (二)企业微信消息推送
  15. 小啊呜产品读书笔记001:《邱岳的产品手记-11》第21讲 产品案例分析:Fabulous的精致养成
  16. excel vba 阻塞 先刷新 连接 再 刷新所有透视表
  17. STM32F051 触摸按键功能
  18. 验证 Android 应用链接
  19. 佳能5D4相机断电后继续拍摄了,断电前的视频丢失数据恢复
  20. robots协议相关

热门文章

  1. 飞在空中的仓库再配合无人机送货,沃尔玛新专利厉害了
  2. mes生产管理的定义
  3. [转载] 百科全说——王晓斋:解析中西医应对肝肾问题(10-10-12)
  4. 【博客话题】我与Linux的不解情缘
  5. 交换机和路由器上流量限制
  6. 如何帮助谷歌鉴别内容重复的网页
  7. BPSK、QPSK、MPSK、QAM、16QAM的调制解调Matlab实现
  8. OpenGL开发学习指南二(glfw+glad)
  9. [Linux] killall 、kill 、pkill 命令详解
  10. 高性能I/O设计模式Reactor和Proactor