垃圾回收器
1. 垃圾回收器组合关系:
垃圾回收器组合关系
- Serial GC:串行垃圾回收器,作用于新生代,收集器采用复制算法,搭配 Serial Old GC 共同使用,在 HotSpot JVM 中,使用命令:
- -XX:+UseSerialGC:使用 Serial GC 和 Serial Old GC 组合垃圾回收器
- Serial Old GC:串行垃圾回收器,作用于老年代,收集器采用标记-压缩算法,搭配 Serial GC 共同使用,也可以作为 CMS GC 的兜底垃圾回收器。
- ParNew GC:并行垃圾回收器,作用于新生代,收集器采用复制算法,老年代可以搭配 Serial Old GC 或 CMSGC 共同使用。
- -XX:ParallelGCThreads:限制线程数量,默认开启和 CPU 数据相同的线程数
- -XX:+UseParNewGC:新生代使用 ParNew GC,当前 JVM 已经不推荐使用这个命令
- Parallel Scavenge GC:作用于新生代收集器,收集器采用标记-压缩算法,相比于 ParNew GC,Parallel Scavenge GC 目标则是达到一个可控制的吞吐量,它也被称为吞吐量优先的垃圾收集器,自适应调节策略也是 Parallel Scavenge GC 与 ParNew GC 的一个重要区别,Parallel Scavenge GC 可以 Parallel Old GC 用于老年代的收集。
- -XX:UseParallelGC:使用 Parallel Scavenge GC + Parallel Old GC 组合策略,也是 JDK6-JDK8 的默认垃圾回收器
- -XX:UseParallelOldGC:与 -XX:UseParallelGC 互相关联,启动一个另一个也会被开启
- -XX:ParallelGCThreads:设置年轻代 Parallel Scavenge GC 线程数
- -XX:MaxGCPauseMillis:设置垃圾回收器最大停顿时间(即 STW 的时间),单位是毫秒
- -XX:GCTimeRatio:垃圾收集时间占总时间的比例(= 1 / (N + 1))。用于衡量吞吐量大小。默认 99,也就是垃圾回收时间不超过 1%。,与 -XX:MaxGCPauseMillis 参数有一定矛盾性
- -XX:+UseAdaptiveSizePolicy:设置 Parallel Scavenge GC 具有自适应调节策略。在这种模式下,年轻代的大小、Eden 和 Survivor 的比例、晋升老年代的对象年龄等参数会被自动调整,已达到在堆大小、吞吐量和停顿时间之间的平衡点。
- CMS:并发收集处理器,作用于老年代,收集器采用标记整理算法,在新生代,可以搭配 Serial 或 ParNew 使用,在老年代,如果需要内存碎片整理或使用 CMS 失败,可以使用 Serial Old 兜底。
- -XX:+UseConcMarkSweepGC:指定 CMS 作为老年代垃圾收集器,开启将会自动将 -XX:UseParNewGC 打开
- -XX:CMSInitiatingOccupanyFraction:设置内存使用率阈值,一旦超过该阈值,便开始回收。
- -XX:+UseCMSCompactAtFullCollection:用于指定在执行完 Full GC 后对内存进行压缩整理,以避免内存碎片的产生
- -XX:CMSFullGCBeforeCompaction:设置在执行多少次 Full GC 后对内存空间进行压缩整理
- -XX:ParallelCMSThreads:设置CMS 线程数,默认 (ParallelGCThreads + 3) / 4
- G1:兼具并发和并行特性的垃圾收集器,可以同时作用于老年代和新生代。
- -XX:+UseG1GC:手动指定使用 G1 收集器
- -XX:G1HeapReagionSize:设置每个Region大小,值是 2 的幂,范围 1MB-32MB之间,目标是根据最小 Java 堆大小分出约2048 个区域,默认堆内存的 1/2000
- -XX:MaxGCPauseMillis:设置期望达到的最大GC 停顿时间(JVM 会尽力实现,但不保证达到)默认 200ms
- -XX:ParalledGCThread:设置 STW 工作线程数,最大为 8
- -XX:ConcGCThreads:设置并发标记线程数,一般设置为 ParalledGCThread 的 1/4 左右
- -XX:InitiatingHeapOccupancyPercent:设置触发GC周期的Java 堆占用率阈值,超过此值,触发 GC。默认 45
- -XX:G1MixedGCCountTarget:G1 混合垃圾回收测目标次数
- -XX:G1HeapWastePercent:G1 Region 中垃圾占用率阈值,低于这个阈值的 Region 不会被回收,默认 10%
2. CMS 处理流程:
- 初始标记:这个阶段,程序中所有的工作线程都将会 STW,这个阶段仅仅只标记出 GC Roots 能够直接关联到的对象。标记完成恢复工作线程,所以这里速度非常快。
- 并发标记:从 GC Roots 的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长,但不需要 STW,垃圾收集器可以和工作线程并发运行。
- 重新标记:为了修复并发标记期间,因用户程序运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间通常比初始标记时间长一些,但远比并发标记时间短。
- 并发清除:清理删除标记阶段判断的已经死亡的对象,释放内存空间。这个阶段垃圾收集器也可以和工作线程并发运行。JDK5默认68,JDK6及以上默认92。如果内存增长缓慢,可以设大点,反之则可以设小一点。
3. G1 处理流程:Remembered Set
3.1 G1 使用 Remembered Set 原因:一个 Region 不可能是孤立的,一个 Region 中的对象可能被其他任意 Region 中对象引用,此时回收新生代也不得不同时扫描老年代,这样会降低 Minor GC 效率
3.2 使用 Remembered Set 解决方案:
- 无论 G1 还是其他分带收集器,JVM 都是使用 Remembered Set 来避免全局扫描
- 每个 Region 都有一个对应的 Remembered Set,每次 Reference 类型数据写操作时,都会产生一个 Write Barrier 暂时中断操作,然后检查将要写入的引用指向的对象是否和该 Reference 类型数据在不同的 Region(其他收集器:检查老年代对象是否引用了新生代对象),如果不同,通过 CardTable 把相关引用信息记录到引用指向对象所在 Region 对应的 Remembered Set 中,当进行垃圾收集时,在 GC 根节点的枚举范围加入 Remembered Set;就可以保证不进行全局扫描,也不会有遗漏。
4. G1 处理流程:并发标记过程:
- 初始标记阶段:标记从根节点直接可达的对象。这个阶段是 STW 的,并且会触发一次 年轻代 GC
- 根区域扫描:G1 扫描 Survivor 区直接可达的老年代区域对象,并标记被引用的对象,这一过程必须在 young GC 之前完成
- 并发标记:在整个堆中进行并发标记(和应用程序并发执行),此过程可能被 young GC 中断。在并发标记阶段,若发现区域对象中的所有对象都是垃圾,那这个区域会被立即回收。同时,并发标记过程中,会计算每个区域的对象存活的比例
- 再次标记:修正并发标记的标记结果。需要 STW,G1 中采用了比 CMS 更快的初始快照算法 (SATB)
- 独占清理:需要 STW,计算各个区域的存活对象和 GC 回收比例并进行排序,识别可以混合回收的区域,为下一阶段做铺垫,这一阶段并不会实际上去做垃圾收集
- 并发清理:识别并清理完全空闲的区域
5. G1 处理流程:混合回收:
- 并发标记后,老年代中百分百垃圾的内存分段被直接回收,部分为垃圾的内存分段被计算出来,默认情况下,这些老年代内存分段会分 8 次(可以通过 -XX:G1MixedGCCountTarget 设置)被回收。
- 混合回收集包括 1/8 的老年代内存分段,Eden 区内存分段,Survivor 区内存分段。混合回收的算法和年轻代回收算法完全一样,只是回收集多了老年代内存分段。
- 由于老年代中的内存分段默认分 8 次回收,G1 会优先回收垃圾多的内存分段。垃圾占内存分段比例越高,越会被优先回收。并且有一个阈值会决定内存分段是否被回收,-XX: G1MixedGCLiveThresholdPercent,默认为 65%,意思是占内存分段要达到 65%才会被回收。如果垃圾占比太低,意味着存回的对象占比高,在复制的时候会花费更多的时间
- 混合回收并不一定要进行 8 次。有一个阈值 -XX:G1HeapWastePercent,默认为 10%,意思是允许整个堆内存中有 10% 的空间被浪费,意味着如果发现可以回收的垃圾占堆内存的比例低于 10%,则不再进行垃圾回收。
6. G1 优化建议:
- 年轻代避免使用 -Xmn 或 -XX:NewRatio 等相关选项显示设置年轻代大小,如果固定年轻代大小会覆盖暂停时间目标
- 暂时时间目标不要太过严苛,G1 的吞吐量目标是 90% 的应用程序时间和 10% 的垃圾回收时间,如果暂停时间太过严苛会直接影响到吞吐量,且每次暂停时间太短会导致垃圾回收区域变少,多次积累之后出现 Full GC 的频率也会变高