垃圾收集器
如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。
前言
一本好书,每读一遍都会有不同的感受。写读书笔记,一来是便于平时查阅,毕竟技术书籍都比较厚,不方便随时携带;二来是督促自己多读书,理论与实践结合才能不断提升自己。
【深入理解Java虚拟机】阅读笔记: 3.5 垃圾收集器
HotSpot 虚拟机提供了多个作用于不同分代的收集器,如下图所示,如果两个收集器之间存在连线,则说明它们之间可以搭配使用。
并行与并发收集器说明
并行 (Parallel)。指多条垃圾收集器并行工作,但此时用户线程仍然处于等待状态
并发 (Concurrent)。指用户线程与垃圾收集线程同时执行 (但不一定是并行的,可能会交替执行),用户程序在继续运行,而垃圾收集程序运行于另一个 CPU 上
Serial 收集器
- 最基本、发展历史最悠久的收集器
- 是一个单线程的收集器,在进行垃圾收集时,必须暂停其他所有的工作线程
- Client 模式下的默认新生代收集器
- 简单高效,在单 CPU 环境下,由于没有线程交互的开销,收集效率最高
ParNew 收集器
- Serial 收集器的多线程版本
- 与 Serial 收集器的所有控制参数 (-XX:SurvivorRatio、-XX:PretenureSizeThreshold、-XX:HandlePromotionFailure等)、收集算法、Stop The World、对象分配规则、回收策略等都完全一样
- Server 模式下首选的新生代收集器
- 除了 Serial 收集器外,只有它能与 CMS 收集器配合工作
Parallel Scavenge 收集器
- 新生代收集器,使用复制算法并行多线程收集
- 目标是达到一个可控制的吞吐量,适合在后台运算而不需要太多交互的任务。
吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)
- -XX:MaxGCPauseMills 参数用于控制最大垃圾收集时间,参数值为一个大于 0 的毫秒数。收集器将尽可能地保证内存回收花费的时间不超过设定值
- -XX:GCTimeRatio 参数用于设置吞吐量,参数值为一个大于 0 且 小于 100 的整数,表示垃圾收集时间占总时间的比例。默认值为 99,即允许最大 1% (1 / (1+99))的垃圾收集时间
- -XX:+UseAdaptiveSizePolicy 参数打开后不需要制定新生代的大小 (-Xmn)、Eden 与 Survivor 区的比例 (-XX:SurvivorRatio)、晋升老年代对象大小 (-XX:PretenureSizeThreshold) 等细节参数,由虚拟机自动动态调整
Serial Old收集器
- Serial 收集器的老年代版本,使用单线程和 “标记-整理” 算法
- Server 模式下:1. 搭配 Parallel Scavenge 收集器使用。2. 作为 CMS 收集器的后备预案
Parallel Old 收集器
- Parallel Scavenge 收集器的老年代版本,使用多线程和 “标记-整理” 算法
- 吞吐量优先收集器
CMS 收集器
运作过程分为 4 个步骤
- 初始标记 (CMS initial mark)。需要 “Stop The World”,标记 GC Roots 能直接关联到的对象
- 并发标记 (CMS concurrent mark)。进行 GC Roots Tracing
- 重新标记 (CMS remark)。需要 “Stop The World”,修正并发标记期间因用户程序继续运行而导致的标记变动记录
- 并发清除 (CMS concurrent sweep)
优点
- 并发收集。耗时最长的并发标记和并发清除过程,收集器线程可以与用户线程一起工作
- 低停顿
缺点
- 对 CPU 资源非常敏感。默认启动回收线程数是 (CPU 数量 + 3) /4,当 CPU 数 不足4个时,对用户程序的影响可能变得很大
- 无法处理浮动垃圾 (Floating Garbage),可能出现 “Concurrent Mode Failure” 失败而导致另一次 Full GC 的产生,这时将临时启用 Serial Old收集器重新进行老年代的垃圾回收。因此 -XX:CMSInitiationOccupancyFraction (老年代使用了百分之多少时触发 CMS 垃圾回收) 设置太高会导致性能降低
- 基于 “标记-清除” 算法,收集结束时会有大量空间碎片产生。**-XX: +UseCMSCompactAtFullCollection** (要进行 FullGC 时开启内存碎片整理)。**-XX:CMSFullGCsBeforeCompaction** (执行多少次不压缩的 FullGC 后来一次带压缩的,默认值为 0)
G1 收集器
优点
- 并行与并发。使用多个 CPU 来缩短 “Stop The World” 停顿的时间,部分其他收集器需要停顿的 GC 动作,G1 收集器可通过并发的方式让用户程序继续执行
- 分代收集。采用不同方式处理新对象和已经存活一段时间、熬过多次 GC 的旧对象
- 空间整合。整体上基于 “标记-整理” 算法,局部上基于 “复制” 算法,不会产生内存空间碎片
- 可预测的停顿。建立可预测的停顿时间模型,让使用者指定在 M 毫秒的时间片段内,垃圾收集时间不超过 N 毫秒
执行步骤
不考虑维护 Remembered Set的操作,大致可划分为以下几个步骤
- 初始标记 (Initial Marking)
- 并发标记 (Concurrent Marking)
- 最终标记 (Final marking)
- 筛选回收 (Live Data Counting and Evacuation)
理解 GC 日志
GC 发生的时间: [垃圾收集的停顿类型 [GC 发生的区域: GC 前该内存区域已使用容量 -> GC 后该内存区域已使用容量 (该内存区域总容量), 该内存区域 GC 所用时间] GC 前 Java 堆已使用容量 -> GC 后 Java 堆已使用容量 (Java 堆总容量), Java 堆 GC 所用时间]
垃圾收集器参数
参数 | 描述 |
---|---|
UseSerialGC | 使用 Serial + Serial Old 收集器组合 |
UseParNewGC | 使用 ParNew + Serial Old 收集器组合 |
UseConcMarkSweepGC | 使用 ParNew + CMS + Serial Old收集器组合。CMS 收集器出现 Concurrent Mode Failure 时使用Serial Old回收内存 |
UseParallelGC | 使用 Parallel Scavenge + Serial Old (PS MarkSweep) 收集器组合 |
UseParallelOldGC | 使用 Parallel Scavenge + Parallel Old 收集器组合 |
SurvivorRatio | 新生代中 Eden 区域 与 Survivor 区域的容量比值,默认为 8,代表 Eden : Survivor = 8 : 1 |
PretenureSizeThreshold | 直接晋升到老年代的对象大小,大于这个参数的对象将直接在老年代分配 |
MaxTenuringThreshold | 晋升到老年代的对象年龄。每个对象经过一次 Minor GC 后年龄加 1,当超过这个参数时就进入老年代 |
UseAdaptiveSizePolicy | 动态调整 Java 堆中各个区域的大小以及进入老年代的年龄 |
HandlePromotionFailure | 是否允许分配担保失败,即老年代的剩余空间不足以应付新生代的整个 Eden 和 Survivor 区的所有对象都存活的极端情况 |
ParallelGCThreads | 设置并行 GC 时进行内存回收的线程数 |
GCTimeRatio | GC 时间栈总时间的比率,默认 99,即允许 1% 的GC时间,仅在使用 Parallel Scavenge 收集器时生效 |
MaxGCPauseMills | 设置 GC 的最大停顿时间,仅在使用 Parallel Scavenge 收集器时生效 |
CMSInitiatingOccupancyFraction | 设置 CMS 收集器在老年代空间被使用多少后触发垃圾收集 |
UseCMSCompactAtFullCollection | 设置 CMS 收集器在完成垃圾收集后是否要进行一次内存碎片整理 |
CMSFullGCsBeforeCompaction | 设置 CMS 收集器在进行若干次垃圾收集后再启动一次内存碎片整理 |
参考
周志明. 深入理解Java虚拟机
- 2017-10-22
Java 内存自动管理可归结为自动化地解决了两个问题:给对象分配内存以及回收分配给对象的内存。
- 2017-10-20
主要介绍常见垃圾收集算法的思想。
- 2017-10-21
垃圾回收算法在 HotSpot 虚拟机上的实现。
- 2019-09-29
java
命令用于启动 java 应用:它首先会启动 java 运行时环境(JRE),然后加载指定的类,调用类的main()
方法。main()
方法必须定义为public
和static
的,并且不返回任何值,参数是 String 类型的数组,该方法的形式如下:1
public static void main(String[] args)