JVM 进阶 (五):JAVA GC 之标记
一、前言
堆分为年轻代和年老代。永久代是非堆内存,它又叫做方法区(一般的说法),主要存储已被加载的类信息、常量、静态变量。而该区域在java8
已被删除,取而代之的是元空间,会在后面的章节细讲。
二、什么是标记?怎么标记?
第一个问题相信大家都知道,标记就是对一些已死的对象打上记号,方便垃圾收集器 GC 的清理。 至于怎么标记,一般有两种方法:引用计数和可达性分析。
2.1 引用计数
引用计数实现起来比较简单,就是给对象添加一个引用计数器,每当有一个地方引用它时就加 1,引用失效时就减 1,当计数器为 0 的时候就标记为可回收。这种判断效率很高,但是很多主流的虚拟机并没有采用这种方法,主要是因为它很难解决几个对象之间循环引用的问题,像下面这个例子会发生循环引用。虽然不怎么用了,但还是值得我们学习!
2.2 可达性分析
可达性分析的基本思路就是:通过将一些称为"GC Roots
"的对象作为起始点,从这些节点开始搜索,搜索和该节点发生直接或者间接引用关系的对象,将这些对象以链的形式组合起来,形成一张“关系网”,又叫做引用链。最后垃圾收集器就回收那些不在这张关系网上的对象。如图:
连接GC Roots
对象的object
是确定还存活的对象,而右边的 die obj 由于和GC ROOTS
没有关系,所以会标记为可回收的对象。目前主流的商用虚拟机用的都是类似的方法。那什么对象才能作为“GC Roots
”呢?在java
中,有四种对象可以作为“GC Roots
”。
栈帧中的引用对象。(栈中的)
静态属性引用的对象。(方法区中的)
常量引用的对象。(方法区中的)
本地方法栈中
JNI
引用的对象。(本地方法栈中的)
目前来说引用计数、可达性分析这两种标记的方法最为常用,带标记完成后就可以进行上一博文《JVM进阶(三):内存分配与回收策略》所讲的回收方法了!
三、拓展阅读
《JVM虚拟机专栏》
四、延伸阅读 利用 MAT 排查堆溢出
在前期博文中,堆已经讲得差不多啦,这章我们以一个例子来说说如何设置以及当发生堆溢出的时候怎么排查问题。先看一小段代码:
4.1 参数设置
上面的代码中使用了一个无限循环来为 list 添加对象,如果采用默认的堆大小的话可能要等待好久才能出现堆溢出的错误,因此我们要将其设置小一点:-Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError
,elipse
中的设置方法我们在前期博文《JVM进阶(一):初识 JAVA 栈》讲过了,这里不多说啦。-Xms10m
,意思就是堆的最小内存为 10m。同理-Xmx10m
的意思就是最大内存也为 10m。这两个都是设置为 10m,那么堆的大小就是 10m。而-XX:+HeapDumpOnOutOfMemoryError
指的是当发生内存溢出的时候会将当前的内存使用情况生成一个快照保存起来,但需要eclipse
下载一个MAT
插件,下载方式自行百度。
4.2 问题排查
使用刚刚设置的参数启动程序,稍等一小会就会出现以下错误:
显示出堆溢出错误,并且生成了一个叫做java_pid4792.hprof
的文件,我们刷新项目便可以看到该文件,双击打开,需要一段的加载时间。
首先可以看到的是一个饼状图,占用部分最大的便是发生溢出错误的部分,我们接着往下看 :
我们这里看看画红线的部分,这里列出一些存活的大对象,在溢出的时候一般先怀疑大对象,我们点进去:
这里列出占用内存最大的几个对象,很显然,第一个很可疑,占用率达到了 94.64%!继续跟进:
发现都是 test 对象,因此排查的时候可以从这个方面下手。接着我们右键该对象选择Path To GC Root
(在引用链上的路径),再选择exclue all phantom/weak/soft etc. reference
,结果如图:
可以看到是被 List 引用了,因此一直在引用链上,导致无法被回收掉,也就出现了内存溢出。本文只讲了MAT
的一个最基本的用途,指出排查思路,感兴趣的小伙伴可以结合工作上的案例自己深入的去了解其更多的用途。
版权声明: 本文为 InfoQ 作者【No Silver Bullet】的原创文章。
原文链接:【http://xie.infoq.cn/article/51e4e806d5d987bbfc0dcffd4】。文章转载请联系作者。
评论