5.垃圾回收器
常用组合:
1.Serial和Serial Old Serial 使用单线程进行垃圾回收,并且会停止所有工作线程(STW),停顿时间长,故现在用的很少(用于回收新生代) 。Serial Old 用于老年代,使用标记清理算法,也是单线程。
2.Parallel Scavenge和 Parallel Old
若JVM没有做任何调优,就是默认的这一组。PS 使用多线程清理垃圾,用于新生代。 PO使用整理算法。 3.ParNew和CMS
ParNew 跟PS没有区别
CMS: 1.基于"标记-清除"算法(不进行压缩操作,产生内存碎片); 2.以获取最短回收停顿时间为目标; 3.并发收集、低停顿; 是HotSpot在JDK1.5推出的第一款真正意义上的并发(Concurrent)收集器;第一次实现了让垃圾收集线程与用户线程(基本上)同时工作; CMS产生的阶段: 1.初始标记STW开始标记。 2.并发标记和应用程序同时运行。 3.重新标记STW,在并发标记中产生的新垃圾在重新标记. 4.并发清理。CMS缺点:1.产生内存碎片. 2.产生浮动垃圾。在并发清理中还会产生垃圾。
4.G1
G1是一种服务端应用使用的垃圾回收器,目标是用在多核,大内存的机器上,它在大多数情况上可以实现指定的GC暂停世界,同时还能保持较高的吞吐量.
G1将内存划分成了多个大小相等的Region(默认是512K),Region逻辑上连续,物理内存地址不连续。同时每个Region被标记成E、S、O、H,分别表示Eden、Survivor、Old、Humongous。其中E、S属于年轻代,O与H属于老年代。
H表示Humongous。从字面上就可以理解表示大的对象(下面简称H对象)。 当分配的对象大于等于Region大小的一半的时候就会被认为是巨型对象。H对象默认分配在老年代,可以防止GC的时候大对象的内存拷贝。通过如果发现堆内存容不下H对象的时候,会触发一次GC操作。 在进行Young GC的时候,Young区的对象可能还存在Old区的引用, 这就是跨代引用的问题。 为了解决Young GC的时候扫描整个老年代,G1引入了Card Table 和Remember Set的概念,基本思想就是用空间换时间。这两个数据结构是专门用来处理Old区到Young区的引用。Young区到Old区的引用则不需要单独处理,因为Young区中的对象本身变化比较大,没必要浪费空间去记录下来。
RSet:全称Remembered Sets, 用来记录外部指向本Region的所有引用,每个Region维护一个RSet。RSet的价值在于使得垃圾回收器不需要扫描整个堆 找到谁引用了当前分区中的对象,只需要扫描RSet即可.
Card Table:如果Old区中的对象指向了Young区,就将它设为Dirty(脏的),下次扫描时,只需要扫描Dirty Card,在结果上Card Table使用了Bitmap.
4.1 G1 GC主要可以分为两个阶段4.1.1 全局并发标记(global concurrent marking) 全局并发标记又可以进一步细分成下面几个步骤:
- 初始标记(initial mark,STW)。它标记了从GC Root开始直接可达的对象。初始标记阶段借用young GC的暂停,因而没有额外的、单独的暂停阶段。
- 并发标记(Concurrent Marking)。这个阶段从GC Root开始对heap中的对象标记,标记线程与应用程序线程并行执行,并且收集各个Region的存活对象信息。过程中还会扫描上文中提到的SATB write barrier所记录下的引用。
- 最终标记(Remark,STW)。标记那些在并发标记阶段发生变化的对象,将被回收。
- 清除垃圾(Cleanup,部分STW)。这个阶段如果发现完全没有活对象的region就会将其整体回收到可分配region列表中。 清除空Region。
漏标:当黑色对象A指向白色对象D,灰色对象B指向白色没了.如下图,这样会产生漏标,遍历不到。必须具备两个两个条件:黑色指向白色对象,灰色指向白色对象的引用消失.
CMS和G1的核心就在于并发标记的线程和工作线程同时进行,只有这个阶段会产生漏标.
两种解决方案:
1.增量更新,关注引用的增加,把黑色A对象变成灰色,下次需要重新扫描属性.(CMS使用).
2.STAB 关注引用的删除。当B指向D的引用消失时,把这个引用推到GC的堆栈,保证D还能被扫描到,下次扫描直接能扫白色,不会产生漏标(G1使用).
当灰色到白色引用消失时,由于有RSet存在,不需要扫描整个堆去查找指向白色的引用,效率比较高.