JVM垃圾回收机制与常用调优参数详解

AI-摘要
神经蛙 GPT
AI初始化中...
介绍自己 🙈
生成本文简介 👋
推荐相关文章 📖
前往主页 🏠
前往爱发电购买
JVM垃圾回收机制与常用调优参数详解
神经蛙垃圾回收(Garbage Collection,简称 GC)是 JVM 最重要的特性之一,也是 Java 面试的必考点。理解 GC 原理不仅能帮你写出更高效的代码,还能在生产环境出现 OOM、CPU 飙高等问题时快速定位根因。
一、JVM 内存区域划分
1.1 运行时数据区
1 | JVM 运行时数据区 |
1.2 堆内存分代模型
堆是 GC 的主战场,经典分代模型分为:
1 | 堆内存(Heap) |
- 年轻代:存放新创建的对象,垃圾回收频率高、速度快
- 老年代:存放存活时间长的对象,回收频率低、耗时久
- 默认比例:年轻代 : 老年代 = 1 : 2(可通过
-XX:NewRatio调整) - Eden : S0 : S1 = 8 : 1 : 1
二、如何判断对象可以回收
2.1 引用计数法
- 原理:每个对象维护一个引用计数器,被引用+1,引用失效-1
- 缺点:无法解决循环引用问题(A 引用 B,B 引用 A,都为 0 但无法回收)
- JVM 没有采用这种方式
2.2 可达性分析算法(JVM 采用)
从一组 GC Roots 根对象出发,向下搜索引用链:
- 能到达的对象 → 存活
- 不可达的对象 → 可回收
GC Roots 包括哪些?
- 虚拟机栈中引用的对象(局部变量)
- 方法区中静态变量引用的对象
- 方法区中常量引用的对象
- 本地方法栈中 JNI 引用的对象
- 被同步锁持有的对象
2.3 四种引用类型
| 引用类型 | 回收时机 | 用途 |
|---|---|---|
| 强引用(默认) | 从不回收,宁可 OOM | 普通对象引用 |
| 软引用 SoftReference | 内存不足时回收 | 缓存、图片内存缓存 |
| 弱引用 WeakReference | 下次 GC 一定回收 | ThreadLocal、WeakHashMap |
| 虚引用 PhantomReference | 任何时候都可能回收 | 管理堆外内存 |
三、经典垃圾回收算法
3.1 标记-清除算法(Mark-Sweep)
两个阶段:标记所有存活对象 → 清除未标记对象
- 优点:实现简单,不需要移动对象
- 缺点:产生大量内存碎片,大对象可能找不到连续空间
3.2 复制算法(Copying)
原理:将内存分为两块,每次只用一块。回收时把存活对象复制到另一块,然后清空当前块。
- 年轻代使用的算法(Eden → Survivor)
- 优点:没有内存碎片,运行高效
- 缺点:可用内存只有一半,空间利用率低
3.3 标记-整理算法(Mark-Compact)
原理:标记存活对象 → 将所有存活对象向一端移动 → 清理边界外内存
- 老年代使用的算法
- 优点:没有内存碎片
- 缺点:需要移动对象,STW 时间长
3.4 分代收集算法
不是新算法,而是根据不同年代特点组合使用:
- 年轻代:对象存活率低 → 复制算法(高效)
- 老年代:对象存活率高、空间大 → 标记-清除 或 标记-整理
四、垃圾收集器
4.1 经典收集器概览
| 收集器 | 年代 | 算法 | 特点 | JDK版本 |
|---|---|---|---|---|
| Serial | 年轻代 | 复制 | 单线程,STW 长 | 早期 |
| ParNew | 年轻代 | 复制 | Serial 多线程版本 | 1.3+ |
| Parallel Scavenge | 年轻代 | 复制 | 吞吐量优先 | 1.4+ |
| Serial Old | 老年代 | 标记-整理 | 单线程 | 早期 |
| Parallel Old | 老年代 | 标记-整理 | 多线程吞吐量优先 | 1.6+ |
| CMS | 老年代 | 标记-清除 | 低延迟,并发收集 | 1.5 ~ 14 |
| G1 | 全代 | 复制+标记整理 | 区域化,可预测停顿 | 1.7+,JDK9默认 |
| ZGC | 全代 | 着色指针 | 超低延迟(<1ms) | JDK11+,JDK15正式 |
| Shenandoah | 全代 | Brooks指针 | 超低延迟 | JDK12+ |
4.2 CMS 收集器(Concurrent Mark Sweep)
以获取最短回收停顿时间为目标,适合互联网应用。
四个阶段:
- 初始标记(STW):只标记 GC Roots 直接关联对象,很快
- 并发标记:与用户线程并发,遍历整个引用链
- 重新标记(STW):修正并发标记期间变动的对象
- 并发清除:与用户线程并发,清除垃圾对象
缺点:
- 标记-清除产生内存碎片
- 并发阶段占用 CPU 资源
- 无法处理”浮动垃圾”
4.3 G1 收集器(Garbage First)
JDK 9 及之后的默认收集器。
核心特点:
- Region 分区:把堆分成多个大小相等的独立区域
- 可预测停顿:可以设置最大 GC 停顿时间(默认 200ms)
- 优先回收价值高的区域:根据回收收益排序,优先回收垃圾多的 Region
- Humongous 区域:大对象直接进入老年代专用区域
GC 模式:
- Young GC:年轻代 Region 回收
- Mixed GC:年轻代 + 部分老年代 Region 一起回收
- Full GC:极端情况触发(应尽量避免)
4.4 ZGC 收集器
JDK 15 正式发布的下一代低延迟收集器。
- 目标:TB 级堆内存,停顿时间 < 1ms,且不随堆增大而增加
- 核心技术:着色指针(Colored Pointers)、读屏障
- 全并发:几乎所有阶段都和用户线程并发执行
- 适用场景:大内存、低延迟要求的服务(如微服务网关、实时计算)
五、GC 日志解读
5.1 开启 GC 日志
1 | # JDK 8 |
5.2 Young GC 日志示例
1 | [GC (Allocation Failure) |
解读:
PSYoungGen:年轻代使用 Parallel Scavenge 收集器65536K->8192K:回收前年轻代 64M → 回收后 8M(76288K):年轻代总大小约 74.5M65536K->12288K:回收前整个堆 64M → 回收后 12M0.0083420 secs:本次 GC 耗时 8ms
六、常用 JVM 调优参数
6.1 堆内存设置
| 参数 | 说明 | 示例 |
|---|---|---|
-Xms |
初始堆大小 | -Xms2g |
-Xmx |
最大堆大小 | -Xmx4g |
-Xmn |
年轻代大小 | -Xmn1g |
-XX:NewRatio |
年轻代与老年代比例 | -XX:NewRatio=2(1:2) |
-XX:SurvivorRatio |
Eden 与 Survivor 比例 | -XX:SurvivorRatio=8(8:1:1) |
-XX:MaxTenuringThreshold |
晋升老年代年龄阈值 | -XX:MaxTenuringThreshold=15 |
6.2 收集器选择
1 | # JDK 8 推荐:Parallel Scavenge + Parallel Old(默认) |
6.3 元空间设置
1 | -XX:MetaspaceSize=256m # 元空间初始大小 |
JDK 8 用元空间(Metaspace)替代了永久代(PermGen),使用本地内存,默认无上限。
6.4 其他重要参数
1 | -XX:+HeapDumpOnOutOfMemoryError # OOM 时自动 dump 堆快照 |
七、调优思路与常见问题
7.1 调优基本思路
- 先监控再调优:不要盲目调参,先通过 GC 日志、JConsole、VisualVM 等工具定位问题
- 目标明确:是追求吞吐量还是低延迟?两者不可兼得
- 从小到大:先调整堆大小,再选收集器,最后微调细节参数
- 压测验证:每次调整都要压测对比效果
7.2 常见 GC 问题排查
频繁 Young GC
- 原因:年轻代太小,对象频繁 Eden 区满
- 解决:适当增大
-Xmn年轻代空间
Full GC 频繁
- 可能原因:
- 老年代空间不足 → 增大堆内存
- 大对象过多 → 检查代码,避免一次性创建大对象
- 内存泄漏 → 用 MAT 分析堆 dump
CPU 100%
top找到高 CPU 进程top -Hp pid找到高 CPU 线程jstack pid查看线程栈- 结合 GC 日志判断是否 GC 线程占用过高
7.3 内存泄漏常见场景
- 静态集合类(static List/Map)只加不删
- 各种连接(数据库、网络)未关闭
- 监听器注册后未注销
- ThreadLocal 使用完未 remove
- 内部类持有外部类引用
评论
匿名评论隐私政策
✅ 你无需删除空行,直接评论以获取最佳展示效果













