【JVM】Java虚拟机(二)——垃圾回收
目录
一、如何判断对象可以回收
(一)引用计数法
(二)可达性分析算法
二、垃圾回收算法
(一)标记清除
(二)标记整理
(三)复制
(四)分代垃圾回收
(五)相关 VM 参数
三、垃圾回收器
(一)串行(Serial)
(二)吞吐量优先
(三)响应时间优先
(四)G1
核心特点
(一)G1 垃圾回收阶段
(二)核心阶段详解
(1) 年轻代GC(Young GC)
(2) 并发标记周期
(3) 混合回收(Mixed GC)
(三)G1关键调优参数
基础配置
区域配置
并发周期控制
四、垃圾回收调优
(一)调优领域
(二)确定目标
(三)最快的 GC
(四)新生代调优
(五)老年代调优
一、如何判断对象可以回收
(一)引用计数法
给对象中添加一个引用计数器:
- 每当有一个地方引用它,计数器就加 1;
- 当引用失效,计数器就减 1;
- 任何时候计数器为 0 的对象就是不可能再被使用的。
这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,其最主要的原因是它很难解决对象之间循环引用的问题。
所谓对象之间的相互引用问题,如下面代码所示:除了对象 objA
和 objB
相互引用着对方之外,这两个对象之间再无任何引用。但是他们因为互相引用对方,导致它们的引用计数器都不为 0,于是引用计数算法无法通知 GC 回收器回收他们。
public class ReferenceCountingGc {Object instance = null;public static void main(String[] args) {ReferenceCountingGc objA = new ReferenceCountingGc();ReferenceCountingGc objB = new ReferenceCountingGc();objA.instance = objB;objB.instance = objA;objA = null;objB = null;}
}
(二)可达性分析算法
Java通过GC Roots
对象作为起点,向下搜索引用链。若对象与GC Roots
无引用链相连,则判定为可回收。
GC Roots包括:
-
虚拟机栈中引用的对象(如局部变量)
-
方法区中类静态属性引用的对象
-
方法区中常量引用的对象
-
本地方法栈中JNI引用的对象
-
同步锁持有的对象
-
活跃线程对象
-
引用类型与回收策略
引用类型 回收条件 示例 强引用 永不回收(除非显式置为 null
)Object obj = new Object()
软引用(Soft) 内存不足时回收 SoftReference<Object> ref
弱引用(Weak) 下次GC必然回收 WeakReference<Object> ref
虚引用(Phantom) 仅用于回收跟踪 PhantomReference<Object> ref
-
对象死亡过程
1. 强引用
- 只有所有 GC Roots 对象都不通过【强引用】引用该对象,该对象才能被垃圾回收
2. 软引用(SoftReference)
- 仅有软引用引用该对象时,在垃圾回收后,内存仍不足时会再次出发垃圾回收,回收软引用对象
- 可以配合引用队列来释放软引用自身
3. 弱引用(WeakReference)
- 仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象
- 可以配合引用队列来释放弱引用自身
4. 虚引用(PhantomReference)
- 必须配合引用队列使用,主要配合 ByteBuffer 使用,被引用对象回收时,会将虚引用入队, 由 Reference Handler 线程调用虚引用相关方法释放直接内存
5. 终结器引用(FinalReference)
- 无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用入队(被引用对象 暂时没有被回收),再由 Finalizer 线程通过终结器引用找到被引用对象并调用它的 finalize 方法,第二次 GC 时才能回收被引用对象
二、垃圾回收算法
(一)标记清除
定义: Mark Sweep
- 速度较快
- 会造成内存碎片
(二)标记整理
定义:Mark Compact
- 速度慢
- 没有内存碎片
(三)复制
定义:Copy
- 不会有内存碎片
- 需要占用双倍内存空间
(四)分代垃圾回收
-
年轻代(Young Generation)
-
对象分配:新对象在Eden区创建
-
回收算法:复制算法(Minor GC)
-
过程:
-
Eden(伊甸园) + S0(From)存活对象复制到S1(To)
-
清空Eden(伊甸园)和S0(From)
-
S0(From)与S1(To)角色交换
-
-
晋升条件:对象年龄(经历GC次数)达到阈值(默认15),或Survivor区空间不足
-
-
老年代(Old Generation)
-
存放长期存活对象
-
回收算法:标记-清除或标记-整理(Major GC/Full GC)
-
触发条件:年轻代晋升失败或空间不足
-
- 对象首先分配在伊甸园区域
- 新生代空间不足时,触发 Minor GC,伊甸园和 From 存活的对象使用copy复制到 To 中,存活的对象年龄加1并且交换 From 和 To
- Minor GC 会引发 Stop The World,暂停其它用户的线程,等垃圾回收结束,用户线程才恢复运行
- 当对象寿命超过阈值时,会晋升至老年代,最大寿命是15(4bit)
- 当老年代空间不足,会先尝试触发 Minor GC,如果之后空间仍不足,那么触发 Full GC,STW的时间更长
算法 | 原理 | 优点 | 缺点 |
---|---|---|---|
标记-清除 | 1. 标记所有活动对象 2. 清除未标记对象 | 实现简单 | 内存碎片化,分配效率低 |
复制 | 内存分为两块,每次使用一块。GC时将存活对象复制到另一块,清空当前块。 | 无碎片,高效 | 内存利用率仅50% |
标记-整理 | 1. 标记活动对象 2. 将存活对象向内存一端移动 3. 清理边界外内存 | 无碎片,内存利用率高 | 对象移动开销大 |
分代收集 | 结合多种算法,按对象生命周期分区管理 | 综合性能最优(主流方案) | 实现复杂 |
(五)相关 VM 参数
含义 | 参数 |
---|---|
堆初始大小 | -Xms |
堆最大大小 | -Xmx 或 -XX:MaxHeapSize=size |
新生代大小 | -Xmn 或 (-XX:NewSize=size + -XX:MaxNewSize=size ) |
幸存区比例(动态) | -XX:InitialSurvivorRatio=ratio 和 -XX:+UseAdaptiveSizePolicy |
幸存区比例 | -XX:SurvivorRatio=ratio |
晋升阈值 | -XX:MaxTenuringThreshold=threshold |
晋升详情 | -XX:+PrintTenuringDistribution |
GC详情 | -XX:+PrintGCDetails -verbose:gc |
FullGC 前 MinorGC | -XX:+ScavengeBeforeFullGC |
三、垃圾回收器
1. 串行(Serial)
- 单线程
- 堆内存较小,适合个人电脑
2. 吞吐量优先(Parallel Scavenge)
- 多线程
- 堆内存较大,多核 cpu
- 让单位时间内,STW 的时间最短 0.2 0.2 = 0.4,垃圾回收时间占比最低,这样就称吞吐量高
3. 响应时间优先(CMS)
- 多线程
- 堆内存较大,多核 cpu
- 尽可能让单次 STW 的时间最短 0.1 0.1 0.1 0.1 0.1 = 0.5
(一)串行(Serial)
-XX:+UseSerialGC = Serial + SerialOld
(二)吞吐量优先(Parallel Scavenge)
-XX:+UseParallelGC ~ -XX:+UseParallelOldGC
-XX:+UseAdaptiveSizePolicy
-XX:GCTimeRatio=ratio
-XX:MaxGCPauseMillis=ms
-XX:ParallelGCThreads=n
(三)响应时间优先(CMS)
-XX:+UseConcMarkSweepGC ~ -XX:+UseParNewGC ~ SerialOld
-XX:ParallelGCThreads=n ~ -XX:ConcGCThreads=threads
-XX:CMSInitiatingOccupancyFraction=percent
-XX:+CMSScavengeBeforeRemark
(四)G1
G1 (Garbage-First) 是Java 7引入、Java 9成为默认的垃圾回收器,专为大内存、低延迟场景设计,替代了传统的CMS回收器。
核心特点
-
分区模型:将堆划分为多个等大小的Region(默认约2048个)
-
分代收集:保留新生代/老年代概念,但物理不连续
-
可预测停顿:通过
-XX:MaxGCPauseMillis
设置目标停顿时间 -
并发标记:与应用程序线程并行工作
-
混合回收:同时清理新生代和老年代区域
(一)G1 垃圾回收阶段
Young Collection
- 会 STW
Young Collection + CM
- 在 Young GC 时会进行 GC Root 的初始标记
- 老年代占用堆空间比例达到阈值时,进行并发标记(不会 STW),由下面的 JVM 参数决定
-XX:InitiatingHeapOccupancyPercent=percent (默认45%)
Mixed Collection
会对 E、S、O 进行全面垃圾回收
- 最终标记(Remark)会 STW
- 拷贝存活(Evacuation)会 STW
-XX:MaxGCPauseMillis=ms
(二)核心阶段详解
(1) 年轻代GC(Young GC)
-
触发条件:Eden区满时
-
过程:
-
STW(Stop-The-World)开始
-
构造回收集(Collection Set = Eden + Survivor)
-
复制存活对象到新Survivor区
-
年龄达到阈值(默认15)的对象晋升老年代
-
(2) 并发标记周期
-
触发条件:老年代占用超过IHOP阈值(默认45%)
-
阶段组成:
-
初始标记(STW):伴随Young GC进行
-
根区域扫描:扫描Survivor区引用
-
并发标记:标记存活对象(与应用并发)
-
最终标记(STW):处理SATB缓冲区
-
清理(STW):统计完全空闲Region
-
(3) 混合回收(Mixed GC)
-
回收包含年轻代Region + 高收益的老年代Region
-
根据垃圾比例排序Region(Garbage-First原则)
-
多次执行直到满足回收目标
(三)G1关键调优参数
基础配置
参数 | 默认值 | 说明 |
---|---|---|
-XX:+UseG1GC | - | 启用G1回收器 |
-Xms / -Xmx | - | 堆初始/最大大小(建议设相同值) |
-XX:MaxGCPauseMillis | 200ms | 目标最大停顿时间 |
区域配置
参数 | 默认值 | 说明 |
---|---|---|
-XX:G1HeapRegionSize | 1-32MB | Region大小(2的幂次) |
-XX:G1NewSizePercent | 5% | 新生代最小占比 |
-XX:G1MaxNewSizePercent | 60% | 新生代最大占比 |
并发周期控制
参数 | 默认值 | 说明 |
---|---|---|
-XX:InitiatingHeapOccupancyPercent | 45% | IHOP阈值(触发并发标记) |
-XX:G1MixedGCLiveThresholdPercent | 85% | Region存活对象低于此值才回收 |
-XX:G1HeapWastePercent | 5% | 可回收垃圾占比阈值(停止Mixed GC) |
四、垃圾回收调优
(一)调优领域
- 内存
- 锁竞争
- cpu
- 占用 io
(二)确定目标
- 【低延迟】还是【高吞吐量】,选择合适的回收器
- CMS,G1,ZGC
- ParallelGC
- Zing
(三)最快的 GC
答案是不发生 GC
查看 FullGC 前后的内存占用,考虑下面几个问题
数据是不是太多?
- resultSet = statement.executeQuery("select * from 大表 limit n")
数据表示是否太臃肿?
- 对象图
- 对象大小 16 Integer 24 int 4
是否存在内存泄漏?
- static Map map =
- 软
- 弱
- 第三方缓存实现
(四)新生代调优
-
新生代的特点
- 所有的 new 操作的内存分配非常廉价
- TLAB thread - local allocation buffer
- 死亡对象的回收代价是零
- 大部分对象用过即死
- Minor GC 的时间远远低于 Full GC
- 所有的 new 操作的内存分配非常廉价
-
越大越好吗?
-
新生代能容纳所有【并发量*(请求 - 响应)】的数据
-
幸存区大到能保留【当前活跃对象 + 需要晋升对象】
-
晋升阈值配置得当,让长时间存活对象尽快晋升
- -XX:MaxTenuringThreshold = threshold
- -XX:+PrintTenuringDistribution
Desired survivor size 48286924 bytes, new threshold 10 (max 10)age 1: 28992024 bytes, 28992024 total age 2: 1366864 bytes, 30358888 total age 3: 1425912 bytes, 31784800 total …
(五)老年代调优
以 CMS 为例
- CMS 的老年代内存越大越好
- 先尝试不做调优,如果没有 Full GC 那么已经…,否则先尝试调优新生代
- 观察发生 Full GC 时老年代内存占用,将老年代内存预设调大 1/4 ~ 1/3
- -XX:CMSInitiatingOccupancyFraction=percent