作为 Java 开发者,你是否经历过服务突然变慢、卡顿,甚至直接崩溃(OOM)的噩梦?面对那一串串神秘的 JVM 启动参数,是否感到无从下手?
JVM 调优并没有那么玄学。今天,我们用一个**“餐厅经营”**的通俗模型,带你彻底搞懂 JVM 内存管理与垃圾回收(GC)的奥秘。
一、 核心概念:你的程序就是一家餐厅
想象一下,你的 Java 应用就是一家餐厅。
- JVM 堆内存 (Heap) = 顾客用餐区。
- 对象 (Object) = 来吃饭的顾客。
- 垃圾回收 (GC) = 服务员收盘子。
- 元空间 (Metaspace) = 后厨与员工手册(存放类结构、方法定义等,不占用餐区)。
餐厅的经营目标只有两个:
- 吞吐量 (Throughput):单位时间内接待尽可能多的顾客。
- 低延迟 (Low Latency):不要让顾客等太久,服务员收盘子时动作要快,别为了打扫卫生让全场顾客暂停用餐(Stop-The-World)。
二、 内存区域:快餐区 vs VIP 包厢
为了提高效率,聪明的餐厅经理(JVM)把用餐区划分成了两块:
1. 年轻代 (Young Generation) —— “快餐区”
- 特点:这里坐的都是“快进快出”的顾客(比如 HTTP 请求中的临时变量)。
- 命运:绝大多数顾客吃完就走(朝生夕死)。
- 清理方式 (Minor GC):服务员只盯着这一块收盘子,动作极快,几乎不影响营业。
2. 年老代 (Old Generation) —— “VIP 包厢”
- 特点:这里坐的都是“赖着不走”的常客(比如 Spring Bean、缓存、数据库连接池)。
- 来源:只有在快餐区(年轻代)经历了多次清理还没走的顾客,才有资格晋升到 VIP 包厢。
- 清理方式 (Major/Full GC):这是大扫除!通常需要全场暂停(STW),耗时很长。调优的核心目标之一,就是尽量减少这里的清理次数。
三、 垃圾回收器:雇佣什么样的清洁工?
随着 Java 的发展,清洁工团队也在进化:
- Serial GC:只有一个清洁工。适合单人小面馆(单核 CPU),大餐厅用它会累死。
- Parallel GC (JDK 8 默认):一群清洁工一起上。吞吐量高,但一旦要搞大扫除(Full GC),会让所有顾客暂停很久。
- G1 GC (Garbage-First):现代化的智能清洁队。
- 策略:它把餐厅划分为很多个小格子(Region)。
- 优势:它知道哪里垃圾最多(Garbage-First),优先打扫那里。而且它能听你指挥:“老板,每次打扫别超过 200毫秒”,它就会尽量控制时间。
- 适用:内存较大(4GB+)的服务,强烈推荐。
四、 关键参数:怎么配置你的餐厅?
这里有几个“黄金参数”,掌握它们就掌握了 80% 的调优技巧。
1. 决定餐厅大小:堆内存
-Xmx4g:餐厅最大能扩建到 4GB。-Xms4g:餐厅一开业就直接建成 4GB。- 老鸟经验:建议
-Xmx和-Xms设置成一样大。防止餐厅一会儿扩建一会儿拆墙(内存动态伸缩),浪费资源且引发抖动。
2. 管理后厨:元空间 (Metaspace)
-XX:MaxMetaspaceSize=256m:后厨最大 256MB,防止内存泄露把整台服务器撑爆。-XX:MetaspaceSize=256m:注意!这在 JDK 8 中不是初始大小,而是“报警阈值”。- 如果不设,默认只有 21MB。应用启动时类加载很快就会超过 21MB,导致 JVM 误以为不够用了,触发 Full GC 来尝试清理。
- 老鸟经验:设置成和 Max 一样大,可以完美避免启动时的 Full GC,加快启动速度。
- 冷知识:你看到监控里 Metaspace 使用率高达 95%?别慌,那是 JVM 按需分配机制导致的“虚高”,只要没达到 Max 上限就没事。
3. 启用智能经理:G1GC
对于 4GB 以上的堆,直接加上这套组合拳:
# 启用 G1
-XX:+UseG1GC
# 设定目标:每次 GC 停顿尽量不超过 200ms
-XX:MaxGCPauseMillis=200
# 激进优化:年老代达到 35% 时就开始准备清理(防患于未然)
# 默认是 45%,调低它可以减少 Full GC 风险
-XX:InitiatingHeapOccupancyPercent=35
五、 实战演练:怎么看餐厅经营状况?
配置好了,怎么知道效果好不好?JDK 自带了神器 jstat。
命令:jstat -gcutil <pid> 1000
每秒刷新一次 GC 统计信息。
输出示例与解读:
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 100.00 45.54 1.74 94.73 92.05 9 0.644 0 0.000 0.644
- S0/S1/E:年轻代各分区使用率。E 区通常涨得很快,满了就触发 YGC。
- O (Old):最关键指标。
- 健康:数值平稳,或者呈锯齿状(上升 -> GC -> 下降)。
- 危险:一路飙升不回头,直到 100%。这说明有内存泄漏或者年老代太小。
- YGC / YGCT:年轻代 GC 次数 / 总耗时。
- 计算
YGCT / YGC可以得出平均每次停顿时间(如 0.644 / 9 ≈ 71ms),很健康。
- 计算
- FGC:Full GC 次数。理想情况应该是 0,或者只有个位数。如果这个值一直在涨,你的餐厅就要倒闭了。
各字段含义
内存使用率指标(百分比)
| 字段 | 全称 | 含义 | 你的值 | 说明 |
|---|---|---|---|---|
| S0 | Survivor 0 | 第一个幸存者区使用率 | 0.00% | 当前为空 |
| S1 | Survivor 1 | 第二个幸存者区使用率 | 100.00% ⚠️ | 已满载 |
| E | Eden | 伊甸园区使用率 | 45.54% | 正常 |
| O | Old | 老年代使用率 | 1.74% | 很低,健康 |
| M | Metaspace | 元空间使用率 | 94.73% ⚠️ | 接近满载 |
| CCS | Compressed Class Space | 压缩类空间使用率 | 92.05% ⚠️ | 接近满载 |
GC 次数和时间指标
| 字段 | 全称 | 含义 | 你的值 | 说明 |
|---|---|---|---|---|
| YGC | Young GC | 新生代 GC 次数 | 9 次 | 累计发生 9 次 |
| YGCT | Young GC Time | 新生代 GC 总耗时 | 0.644 秒 | 平均每次 71ms |
| FGC | Full GC | 老年代 GC 次数 | 0 次 ✅ | 很好,未发生 |
| FGCT | Full GC Time | Full GC 总耗时 | 0.000 秒 | 未发生 |
| GCT | Total GC Time | GC 总耗时 | 0.644 秒 | YGCT + FGCT |
六、 避坑指南 (Do’s and Don’ts)
✅ Do:
- 一定要设置
-XX:+HeapDumpOnOutOfMemoryError。当餐厅倒闭(OOM)时,它会保留最后的现场照片(Dump 文件),这是你事后分析原因的唯一线索。 - 一定要记录 GC 日志(
-Xloggc:xxx)。这是餐厅的监控录像。 - 尽量让对象在年轻代就“死掉”,别让它们轻易混进年老代。
❌ Don’t:
- 不要随意设置
-Xmn(年轻代大小)给 G1。G1 会自动调整,你定死了反而限制了它的发挥。 - 不要在 JDK 8 里使用 JDK 9+ 的参数(如
G1UseAdaptiveIHOP),会导致服务起不来。 - 不要看到 Metaspace 使用率 98% 就惊慌,先看它有没有达到 Max 上限。

221

被折叠的 条评论
为什么被折叠?



