《深入理解JVM》笔记 第3章 GC
一、垃圾回收机制
Java判断对象是否存活使用了可达性分析算法,也就是以一系列“GC Roots”对象作为起始点,从这些节点向下搜索,如果从GC Roots到这个对象不可达,则证明此对象不可用。
1. 可以作为GC Roots的对象包括:
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI引用的对象
2. 根搜索算法
新生代使用BFS,无回溯操作,速度快
老年代使用DFS,占内存少速度较慢
3. 强软弱虚,依次减弱
4. 生存还是死亡?
当对象被查出不可达时,如果没有重写finalize()则直接回收。如果重写了finalize()并且finalize()还没有被执行过,则进入F-Queue队列,另一个线程会遍历队列,执行这些对象的finalize()方法,执行完了虚拟机会再次判断对象是否可以被回收,如果可以则回收,否则先不回收。
5. 方法区的回收
书中把方法区回收分为废弃常量和无用类。常量池在jdk1.7已经转移到堆中,而常量的回收也和对象类似。而如何判定类是否是“无用的类”则比较复杂,需要满足3个条件:
- 该类的所有实例都已经被回收,也就是Java堆中不存在该类的任何实例
- 加载该类的ClassLoader已经被回收
- 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
另外,是否对类进行回收,HotSpot虚拟机提供了-Xnoclassgc参数进行控制,还可以使用-verboss:class和-XX:+TraceClassLoading查看类加载和卸载信息。
二、垃圾回收算法
1. 标记-清除(Mark-Sweep)其它算法的基础
2. 复制
新生代的对象朝生夕死,采用复制算法。
Eden Survivor1 Survivor2 如果Survivor2不够放,则放到老年代(以老年代为担保)
HotSpot虚拟机默认大小比例是8:1:1
3. 标记-整理
老年代的对象存活率较高,采用标记-整理。即存活对象都向一端移动,然后清理掉端边界以外的内存。
4. 分代收集 现在商业虚拟机都使用的算法
三、HotSpot的算法实现
1. 安全点
可以作为GC Roots的节点主要在全局性的引用(例如静态常量或静态属性)与执行上下文(例如栈帧中的本地变量表)中。
这样逐个检查引用太消耗时间。
于是HotSpot在安全点(Safepoint)记录信息到OopMap,这样可以通过这个OopMap中存储的信息计算出需要扫描的引用。
安全点的选定标准是,是否具有让程序长时间执行的特征。
“长时间执行”的最明显特征就是指令序列复用,例如方法调用、循环跳转、异常跳转等。
2. 如何跑到安全点再停顿
- 抢先式中断:GC发生时停掉所有线程,如果发现有线程不在安全点,则恢复它,让它跑到安全点再停。现在几乎没有虚拟机这么干,太粗暴了。
- 主动式中断:需要中断时设置标志,线程轮询标志,发现标志为真则挂起。
3. 安全区域
安全区域指在一段代码片段中,引用关系不会发生变化。在这个区域中地任意地方开始GC都是安全的(扩展了的安全点)。
三、垃圾收集器
jdk1.7 默认 Parallel Scavenge(新生代)+ Parallel Old(老年代)
jdk1.8 默认 Parallel Scavenge(新生代)+ Parallel Old(老年代)
jdk1.9 默认 G1
1. Serial收集器
单线程,简单高效。JDK1.3.1之前是虚拟机新生代的唯一选择,现在依然是虚拟机运行在Client模式下的默认新生代收集器。使用复制算法。
2. ParNew
Serial收集器的多线程版本。除了使用多线程进行垃圾收集,其余行为与Serial完全一样。单CPU,ParNew不会比Serial效率更高。
只有ParNew和Serial可以配合老年代的CMS。
3. Parallel Scavenge收集器(“吞吐量优先”收集器)
提供了两个参数用于精确控制吞吐量,分别是:
- -XX:MaxGCPauseMillis 控制最大垃圾收集停顿时间(允许大于0的毫秒数)
- -XX:GCTimeRatio 直接设置吞吐量大小(大于0小于100的整数)
注意:并非停顿时间越小越好,GC停顿时间缩短是以牺牲吞吐量和新生代空间来换取的。
另外,Parallel Scavenge收集器有一个自适应开关:-XX:+UseAdaptiveSizePolicy
打开这个参数后,不需要手动指定新生代大小(-Xmn)、Eden于Survivor比例(-XX:SurvivorRatio)、晋升老年代对象大小(-XX:PretenureSizeThreshold),虚拟机会自动调整这些参数。
4. Serial Old收集器
Serial Old是Serial的老年代版本,同样是单线程,使用标记整理算法。主要意义也是在于给Client模式下的虚拟机使用。如果在Server模式下,还有两个用途:
- 在jdk1.5以及之前的版本中,与Parallel Scavenge收集器搭配使用(即Parallel Scavenge中的的PS MarkSweep)
- 作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用
5. Parallel Old收集器(JDK1.6才提供)
Parallel Scavenge收集器的老年代版本,使用标记整理算法。
6. CMS(Concurrent Mark Sweep)
CMS是一种以获取最短回收停顿时间为目标的收集器。从名字可以看出,CMS基于标记-清除算法。整体过程分4个步骤:
- 初始标记 (仅标记GC Roots直接关联对象)
- 并发标记 (GC Roots Tracing)
- 重新标记(修正)
CMS有以下3个明显缺点:
- 占用CPU资源【CMS默认启动的回收线程数是(CPU数+3)/ 4),当CPU不足4个,CMS对用户线程的影响会变得很大】
- CMS无法处理浮动垃圾,可能出现”Concurrent Mode Failure”失败而导致另一次Full GC的产生。
Jdk1.5默认老年代使用了68%时CMS被激活,这个值偏保守。可以设置-XX:CMSInitiatingOccupancyFraction的值来提高触发比例。Jdk1.6这个值提升至92%,如果CMS运行期间预留内存无法满足需要,则出现“Concurrent Mode Failure”失败,虚拟机将临时启用Serial Old收集器来进行老年代垃圾收集。
- 碎片问题,会导致提前进行Full GC
-XX:+UseCMSCompactAtFullCollection 默认开启,CMS在Full GC时开启内存碎片整理,这个过程显然会使停顿时间变长
-XX:CMSFullGCsBeforeCompaction 默认为0 ,设置多少次不压缩的Full GC后来一次带压缩的Full GC
7. G1(Garbage-First)
- G1的特点:并行与并发、分代收集、空间整合(整理基于标记=整理,局部(两个Region之间)基于复制)、可预测的停顿
- G1采用了化整为零的思想:新生代、老年代不再物理隔离,G1将整个堆划分成一个个Region。
- G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region(这也是Garbage-First名称的来由)
G1的大致步骤:初始标记、并发标记、最终标记、筛选回收
四、内存分配与回收策略
1. 对象优先分配在Eden区
Eden区没有空间时,虚拟机发起一次Minor GC。虚拟机提供了-XX:+PrintGCDetails告诉虚拟机发生垃圾收集时打印内存回收日志。
2. 大对象直接进入老年代
-XX:PreTenureSizeThreshold参数(单位为B,比如3MB,这里写3145728)指定多大的对象进入老年代
3. 长期存活的对象进入老年代
-XX:MaxTenuringThrethold 默认15 指定多大的对象进入老年代
4. 动态对象年龄判定
相同年龄大小的所有对象总和大于Survivor空间的一半,则大于等于该年龄的对象就可以直接进入老年代。
5. 空间分配担保
Jdk1.6 Update 24之后,HandlePromotionFailure参数不再使用。只要老年代可用连续空间大于新生代对象总大小或者历次晋升平均大小就会Minor GC,否则Full GC
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南