引用计数器
对象被引用时计数器 + 1,放弃引用时计数器 -1
java 通常不使用此方式
缺点:
复制
主要应用于 JVM 堆的新生代中
JVM 的堆中,新生代分为 3 个区域 Eden、FROM、TO
优点:
因为是整体复制,所以不产生内存碎片
缺点:
浪费空间
大对象复制时比较耗时
标记清除
主要应用于 JVM 堆的老生带中
先标记需要回收的对象
然后统一清除
优点:
避免大批量复制对象,节省内存空间
缺点:
会产生内存碎片
标记整理
主要应用于 JVM 堆的老生代中,不浪费空间,但费时
先标记需要回收的对象
然后在此扫描将对象向一端压缩,使可用内存连续
优点:
避免内存碎片
缺点:
移动对象需要时间
什么是垃圾
内存中不再被使用的空间
如何判断
可达性分析
可以作为 GC Root 的对象
GC Root 是一组明确正在被使用的对象
可以这样理解:
JVM 内存模型一共 5 个区
示例
public class GcRootsDemo {
public static final AAA OBJ_1 = new AAA();// GC root
private static AAA job2 = new AAA(); // GC root
private AAA obj3 = new AAA(); // 只从这里看的话,不一定是垃圾,但不是 GC root
public void m1(){
AAA job4 = new AAA(); // 1
AAA job5 = obj3; // 2
System.out.println();
}
public static void main(String[] args) {
new GcRootsDemo().m1();
}
}
说明:
尚未运行的应用,可以通过 java -XX:+PrintCommandLineFlags
查看
运行中的应用,可以通过 jinfo -flags pid
查看
分类
Serial 收集器 /ˈsɪriəl/
Parallel 收集器 /ˈpærəlel/
CMS(Concurrent Mark Sweep)
G1
ZGC
默认垃圾收集器及部分常见参数
Serial Old
单线程,标记整理算法
是 JVM 在 client 模式的默认老年代垃圾收集器
Server 模式下
Server 模式:JVM 在 win32 与 2C/2G 以下的其他系统上固定为 client 模式(不配作为服务器),其余都是 Server 模式
ParNew
相比 Serial ,ParNew 是它的多线程版
是 Server 模式下,很多 JVM 的默认新生代收集器
在较旧的系统中,常用于协同 CMS / Serial Old
常见参数
-XX:ParallelGCThread
指定线程数,默认 4Parallel Scavenge
相比 ParNew,Parallel Scavenge 具有如下优点
常见参数
-XX:MaxGCPauseMillis
指定最大停顿时间-XX:ParallelGCThread
指定线程数-XX:ParallelGCThread
=核数8 + (cores - 8) * den
,den 就是 那两个比例,通常 JVM 会根据机器和系统自动选择CMS
CMS 收集器旨在 尽量降低停顿时间
相比 Parallel Scavenge,CMS 提供更短的停顿时间
CMS 的垃圾收集的 4 个阶段(但实际是 7 个步骤)
初始标记 Initial Mark
此阶段是 STW 的,不能从根上就乱
标记符合要求的老年代对象(因为是老年代垃圾收集器,年轻代不管),包括
并发标记(以及 并发预清理 和 可终止并发预清理,这三步在本文中归类为同一个阶段) Concurren Mark + PreClean
需要注意,因为并发标记阶段是并发的,对象间的引用关系在时刻变化,所以此阶段 100% 精准的标记所有对象是不现实的
这就需要在后面增补一个阶段(最终标记),对并发标记阶段的工作做一个确认和订正,这个新阶段为了准确性,肯定是 STW 的
因此并发标记阶段的主要目的实际上尽量减少下一个阶段的工作量,使最终标记阶段的 STW 时间尽可能短
而使下一个阶段工作量尽量少的方法,是在并发标记阶段通过三步尽量准确的标记非垃圾对象
结合下文,这里可能有个坑:如何保证并发标记时没有引用变化,没被标记为 dirty 的对象,后面不会 dirty
这种对象如果变为 dirty,从本文梳理的流程上看,是无法感知的,作者只能理解为这部分对象会在最终标记阶段修正
最终标记 Remark
此阶段是 STW 的,否则不能保证 100% 准确
结合上一阶段的结果,做最后的确认和修正,包括如下内容
并发清除 Concurrent Sweep
正常的标记清除算法中的清除部分,因为需要清除的东西都提前标好了所以不用 STW
重置本 GC 周期中变更的状态,比如 dirty card
垃圾、dirty、非垃圾 也会称为白、灰、黑,整个上述过程也被称为 三色标记法
CMS 的缺点
常见参数
-XX:CMSInitiatingOccupancyFraction
设置 GC 预留空间Concurrent Mode Failure
,启用备用的 Serial Old 收集器CMS 相当于将在可达性算法的遍历过程中遇到的对象,划分为层次较深的和较浅的两个部分
层次较浅的部分,通过 STW 的初始标记和最终标记确定
层次较深的部分,通过并发标记三步骤持续跟进,并在最终标记中确认
但还是感觉有点怪,假设有个 GC Root,它直连了两个老生代对象,并在这两个对象间反复横跳。则,这两个对象不会被标记为 dirty
因为 dirty 的处理都依赖于第一次对 dirty 的标记,而第一次 dirty 的标记只在 并发标记这个步骤(不是阶段)中做一次,不循环
然后在确认阶段只能二选一?? 这俩对象如果后面还连着一大串呢??
G1
JDK 7u4 及以上的版本全面支持 G1 垃圾收集器
G1 服务端垃圾收集器,服务对象时多核大内存的机器
兼顾 高吞吐量 与 GC 暂停时间目标的高概率达成(G1 允许设置一个时间长度,作为 GC 暂停时间目标)
设计目标如下:
其他垃圾收集器的通理
相对于 其他垃圾收集器,G1
相对于 CMS ,G1
G1 对 Eden region 的收集
常见参数
-XX:G1HeapRegionSize
设置 region 大小-XX:MaxGCPauseMills
设置 GC 暂停时间的期望值-XX:InitiatingHeapOccupancyPercent
设置触发 GC 的堆占用空间阈值,后面的数字代表百分比-XX:ConcGCThreads
设置 GC 使用的并发线程数-XX:G1ReservePercent
设置 G1 预留空间的百分比名字 | 类别 | 线程 | 并发 | 算法 | 适用区域 | 适用场景 | 说明 |
---|---|---|---|---|---|---|---|
Serial | 串 | 单 | × | 复制 | 新生代 | 非服务器 | |
Serial Old | 串 | 多 | × | 标记整理 | 老生代 | 非服务器 | |
ParNew | 并 | 多 | × | 复制 | 新生代 | 弱交互场景 | Serial 的多线程版本 server模式下,很多 JVM 的默认新生代收集器 |
Parallel Scavenge | 并 | 多 | × | 复制 | 新生代 | 弱交互场景 | 注重吞吐量而不是降低垃圾收集时间 |
Parallel Old | 并 | 多 | × | 标记整理 | 老生代 | 弱交互场景 | |
CMS | 多? | √ | 标记清除 | 老生代 | 强交互场景 | JDK14 中凉了 | |
G1 | G1 | 多 | √ | 标记整理 | 新生代 + 老生代 | 大内存场景 | STW 时间过短可能造成回收效果不理想 |
ZGC | ZGC | 多 | √ | 复制(基于着色指针改进) | 新生代 + 老生代 | 大内存场景 |
组合示意图,与已知过期废除
配置参数 | 组合 | 新生代收集器 | 老生代收集器 | 说明 |
---|---|---|---|---|
-XX:+UseSerialGC | DefNew + Tenured | Serial | Serial Old | |
-XX:+UseSerialOldGC | PSYoungGen + Tenured | Parallel Scavenge | Serial Old | 现已不可用 |
-XX:+UseParNewlGC | ParNew + Tenured | ParNew | Serial Old | JDK 9 废弃了 ParNew+Serial Old JDK 14 移除了 CMS 所以 JDK 14+ 中 ParNew 实际没法用了 |
-XX:+UseParallelGC | PSYoungGen + ParOldGen | Parallel Scavenge | Parallel Old | 默认垃圾收集组合,至少 JDK 8 是 |
-XX:+UseParallelOldGC | PSYoungGen + ParOldGen | Parallel Scavenge | Parallel Old | 与 -XX:+UseParallelGC 互相激活 |
-XX:+UseConcMarkSweepGC | ParNew + CMS + Tenured | ParNew | CMS Serial Old(备胎) | 会激活 -XX:+UseParNewlGC 同时选择 Serial Old 作为老生代收集出错时的后备 |
-XX:+UserG1GC |
区域与垃圾收集器标记可能的值与含义见下表
标记 | 含义 | 对应垃圾收集器 | 区域 |
---|---|---|---|
DefNew | Default New Generation | Serial | 新生代 |
Tenured | Old | Serial Old | 老生代 |
ParNew | Parallel New Generation | ParNew | 新生代 |
PSYoungGen | Parallel Scavenge | Parallel Scavenge | 新生代 |
ParOldGen | Parallel Old Generation | Parallel Old | 老生代 |
CMS | Concurrent Mark Sweep | CMS | 老生代 |
CMS |
单核小内存单机:-XX:+UseSerialGC
多核大吞吐量弱交互:-XX:+UseParallelGC
多核低卡顿强交互: -XX:+UseConcMarkSweepGC
或 -XX:+UseParNewlGC
(老版本)
Young GC(Minor GC)
Full GC
通过 JVM 参数 -XX:+PrintGCDetails
开启 GC 日志
参考 基础 | JVM - [参数]
开启 GC 日志后,运行 java 时会先打印堆信息,如下图
GC 日志示例
[GC (Allocation Failure) [PSYoungGen: 512K->488K(1024K)] 512K->512K(1536K), 0.0011107 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 995K->504K(1024K)] 1019K->644K(1536K), 0.0006667 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 1016K->504K(1024K)] 1156K->771K(1536K), 0.0005018 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 869K->512K(1024K)] 1136K->1003K(1536K), 0.0004885 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 512K->365K(1024K)] [ParOldGen: 491K->290K(512K)] 1003K->656K(1536K), [Metaspace: 3232K->3232K(1056768K)], 0.0027570 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 365K->353K(1024K)] [ParOldGen: 290K->286K(512K)] 656K->639K(1536K), [Metaspace: 3232K->3232K(1056768K)], 0.0027270 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
GC 日志格式
以第一行为例
完整 GC 日志 格式
[GC 信息][Times 信息]
[GC 信息] 格式
[GC (Allocation Failure) [PSYoungGen: 512K->488K(1024K)] 512K->512K(1536K), 0.0011107 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[类型 (状态) [区域 GC 信息] 堆信息, GC 时间]
[类型 (状态)
GC 类型,是 Minor GC 还是 Full GC
[区域 GC 信息] 格式
[PSYoungGen: 512K->488K(1024K)]
[区域与垃圾收集器标记: 收集前大小->收集后大小(总大小)]
区域与垃圾收集器标记可能的值与含义参考 垃圾收集器参数配置与组合
堆信息 格式
512K->512K(1536K)
收集前大小->收集后大小(总大小)
堆信息排在新生代/老生带信息之后,元空间信息之前
GC 时间
本次 GC 使用的时间
[Times 信息] 格式
[Times: 用户耗时 系统耗时, 实际耗时]