jvm垃圾收集器整理
一、分代收集
特点:单线程和多线程、并发(垃圾收集的多线程和应用的多线程同时进行)和并行(垃圾收集的多线程的同时进行)
单线程收集:注意用户线程和GC线程
多线程收集:如下
注意:System.gc()会导致所有的用户线程都在等待
1、新生代收集器
特点:整个新生代使用的都是复制算法
收集器 |
收集对象和算法 |
收集器类型 |
Serial |
新生代,复制算法 |
单线程 |
ParNew |
新生代,复制算法 |
并行的多线程收集器 |
ParallelScavenge |
新生代,复制算法 |
并行的多线程收集器,适合计算 |
2、老年代
收集器 |
收集对象和算法 |
收集器类型 |
Serial Old |
老年代,标记整理算法 |
单线程 |
ParallelOld |
老年代,标记整理算法 |
并行的多线程收集器 |
CMS |
老年代,标记清除算法 |
并行与并发收集器 |
G1 |
跨新生代和老年代;标记整理 + 化整为零 |
并行与并发收集器 |
二、重要垃圾收集器解析
1、CMS收集器:使用标记清除算法,有浮动垃圾和内存碎片(使用较多,主要是快)
步骤如下:
-
初始标记 --暂停所有用户线程,标记哪些有GC Roots
-
并发标记 --同时进行,可以和用户线程一起,从堆中的对象进行线性分析
-
重新标记 --暂停,重新标记,对同时进行的用户线程所产生的垃圾再进行标记
-
并发清除 --同时进行(用户线程和GC线程同时进行)
缺点:(1)CPU资源使用较多
(2)浮动垃圾,重新标记之后,用户线程和GC线程并发清除同时进行而产生的垃圾(要空间存放,JDK1.6达到 92%开启,阈值:8%存放浮动垃圾)
(3)内存碎片
基本命令和配置:
==>jps -v
==> -XX:+UseConMarkSweepGC (+:开启,-:关闭)
-
虚拟机提供了-XX:UseCompactAtFullCollection参数来进行碎片的合并整理过程,这样会使得停顿时间过长;
-
虚拟机还提供了一个参数配置-XX:CMSFullGCsBeforeCompaction,用于设置执行多少次不压缩的Full GC后,接着来一次带压缩的GC。
2、G1收集器:(jdk1.9之后推荐使用,1.7开始存在)
特点:可预测的停顿 ,1000区域 一个区域10m * 100个(急需回收) First.
内存布局的改变,空间整合,化整为零,不会产生内存碎片(如下图)
G1 GC模式:
-
Young GC,只会回收Eden区和Survivor区
-
Mixed GC,类似fullGC
步骤如下:
全局并发标记(Global Concurrent Marking)
-
初始标记 --只有G1线程
-
并发标记 --G1线程和业务线程同时进行
-
最终标记 --只有G1线程,(所有后续步骤不会有浮动垃圾)
-
筛选回收 --只有G1线程(可预测的停顿)
缺点:比较占用内存
最后注意几个问题:
(1)关于CMS和G1 的优缺点和选择比较:
如果是追求速度快,可以选择CMS收集器,但是会有浮动垃圾
如果是追求内存干净无垃圾,可以选择G1收集器,适合预留内存较大的时候使用
(2)GC线程停顿的原因
判断对象是否存活的可达性分析对时间的敏感还体现在GC停顿上,因为可达性分析工作必须在一个能确保一致性的快照中进行,“一致性”的意思是指在整个分析期间整个执行系统看起来就像被冻结在某个时间点上,不可以出现分析过 程中对象引用关系还在不断变化的情况。不然的话可达性分析的结果就无法得到保证。
(3)通过日志看到,系统请求数目很少,每个请求资源也不多,但是系统内存占用非常高,可能出现什么问题?怎样订单问题在哪里?
可能是发生了堆溢出或内存泄露,要解决堆内存异常的情况一般的手段是通过内存映像分析工具(如Eclipse Memory Analyzer)对Dump出来的堆转储快照进行分析,重点是确认内存中的对象是否是有必要的,也就是要先分清楚是内存泄露还是内存溢出。
-
如果是内存泄露,可进一步通过工具查看泄露对象到GC Roots的引用链。于是就能找到泄露的对象是怎么与GC Roots相连接导致进行垃圾回收时没能回收泄露对象所占的内存,有了泄露对象的信息和GC Roots引用链的信息,就可以准确地定位出泄露代码的位置。
-
如果没有发出内存泄露,也是说,内存中的对象确实还活着,那就应该去检查虚拟机的堆参数(-Xmx与-Xms),与物理机器对比看是否还可以进一步扩大,从代码上检查是否存在某些对象声明周期过长、持有状态时间过长等,尝试减少程序运行内存消耗。
(4)安全点机制
程序执行时并不是在所有地方都能停顿下来开始GC,只有到达安全点时才能暂停。其中安全点指的是记录OopMap数据结构的位置。
-
安全点的选择基本上是以程序“是否能让程序长时间执行的特征”为标准进行选定的。
-
“长时间执行”的最明显的特征就是指令序列复用,例如方法调用、循环跳转、异常跳转等,所有具有这些功能的指令才会产生safepoint。
-
对于safepoint,另一个需要考虑的问题就是如何在GC发生时让所有线程都“跑”到最近的安全点上再停顿下来。
-
有两种方案可以选择:抢先式中断和主动式中断。
对于抢占式中断:在GC发生时,首先把所有线程全部中断,如果发现有些线程中断的地方不在安全点上,就恢复线程,让它“跑”到安全点上,现在几乎没有虚拟机采用这种中断方式应GC事件
对于主动式中断:当GC需要中断线程的时候,不直接对线程操作,仅仅简单地设置一个标志,各个线程执行时主动轮询这个标志,发现中断标志为真时,就自己中断挂起。轮询标志的地方和安全点是重合的,另外再加上创建对象需要分配内存的地方
(5)安全区域
安全点机制保证了程序执行时,在不太长的时间内就会遇到可进入GC的安全点。但当线程处于sleep状态或者blocked状态时,这时候线程可能无法响应JVM的中断请求,“走到”安全的地方中断挂起。对于这种情况,就需要安全区域来解决。安全区域是指在一段代码片段之中,引用关系不会发生变化,在这个区域的任何地方开始GC都是安全的。因此也可以把安全区域看做被扩展了的安全点,在线程执行到Safe Region中的代码时,首先标识自己已经进入到了Safe Region,那样,当在这段时间里JVM要发起GC时,就不用管标识自己为Safe Region状态的线程了。在线程要离开Safe Region时,要检查系统是否已经完成了根结点的枚举(或者整个GC过程),如果完成了,那线程就继续执行,否则它就继续等待直到收到可以安全离开Safe Region的信号为止。总之,程序并非是时时刻刻都可以去执行GC操作,只有程序运行到安全区域的时候才可以发起GC的操作