JVM调优实战:从垃圾回收到内存模型,一次性搞定JVM核心知识点
一、JVM内存模型(JMM)
1.1 运行时数据区
┌─────────────────────────────────────┐ │ 运行时数据区 │ ├───────────────┬─────────────────────┤ │ │ 方法区 │ │ 线程共享区域 │ │ │ │ 堆 │ ├───────────────┴─────────────────────┤ │ │ 虚拟机栈 │ │ 线程私有区域 │ │ │ │ 本地方法栈 │ │ │ │ │ │ 程序计数器 │ └─────────────────────────────────────┘1.2 各区域作用
| 区域 | 作用 | 大小 |
|---|---|---|
| 程序计数器 | 记录当前线程执行的字节码行号 | 固定大小 |
| 虚拟机栈 | 方法调用栈帧,存局部变量、操作数栈 | 动态扩展/收缩 |
| 本地方法栈 | native方法调用 | 动态扩展/收缩 |
| 堆 | 对象实例、数组 | 可通过-Xms/-Xmx调整 |
| 方法区 | 类信息、常量、静态变量 | 可通过-XX:MetaspaceSize调整 |
二、垃圾回收算法
2.1 引用计数法(不用)
// 每个对象有个引用计数器// 引用+1,失效-1// 问题:循环引用无法回收ObjectA=newObject();ObjectB=newObject();A.ref=B;B.ref=A;A=null;B=null;// A和B互相引用,但已经不可达,计数却不为02.2 可达性分析(Java用这个)
GC Roots对象: ├── 虚拟机栈中引用的对象 ├── 方法区中类静态属性引用的对象 ├── 方法区中常量引用的对象 ├── 本地方法栈中JNI引用的对象 └── 活跃的线程引用2.3 四种引用类型
// 强引用(永远不会回收)Objectobj=newObject();// 软引用(内存不足时回收)SoftReference<Object>soft=newSoftReference<>(newObject());// 弱引用(下次GC时回收)WeakReference<Object>weak=newWeakReference<>(newObject());// 虚引用(无法通过虚引用获取对象)PhantomReference<Object>phantom=newPhantomReference<>(newObject(),refQueue);三、垃圾回收器
3.1 垃圾回收器分类
┌────────────────────────────────────────────────────┐ │ 新生代(Minor GC) │ │ ───────────────────────────────────────────────── │ │ Serial(串行)│ ParNew(并行)│ Parallel Scavenge │ │ ↓ ↓ ↓ │ ├────────────────────────────────────────────────────┤ │ 老年代(Major/Full GC) │ │ Serial Old│ Parallel Old│ CMS │ G1 │ ZGC │ └────────────────────────────────────────────────────┘3.2 各回收器对比
| 回收器 | 线程 | 策略 | 适用场景 |
|---|---|---|---|
| Serial | 单线程 | 复制 | 客户端模式 |
| ParNew | 多线程 | 复制 | 多核服务端 |
| Parallel Scavenge | 多线程 | 复制 | 吞吐量优先 |
| CMS | 多线程并发 | 标记-清除 | 低延迟 |
| G1 | 多线程并发 | 标记-整理 | 大堆低延迟 |
| ZGC | 多线程并发 | 着色指针 | 极低延迟(<1ms) |
3.3 CMS回收器(重点)
CMS(Concurrent Mark Sweep)是一种并发执行的垃圾回收器:
初始标记(STW)→ 并发标记 → 重新标记(STW)→ 并发清除CMS配置:
-XX:+UseConcMarkSweepGC-XX:CMSInitialMarkingThreshold=100-XX:ConcGCThreads=4-XX:CMSInitiatingOccupancyFraction=70CMS问题:
- 内存碎片:标记-清除算法导致
- 浮动垃圾:并发标记时新产生的垃圾
- concurrent mode failure:浮动垃圾太多时触发
四、G1垃圾回收器
4.1 G1特点
| 特点 | 说明 |
|---|---|
| 逻辑分代 | 仍有新生代老年代概念 |
| 物理不分代 | 内存是连续的小区块(Region) |
| 可预测停顿 | 可以设置停顿时间目标 |
| 整合多种算法 | 复制+标记-整理 |
4.2 G1 Region
┌──────┐┌──────┐┌──────┐┌──────┐┌──────┐ │ Eden ││ Eden ││ S1 ││ S2 ││ Old │ └──────┘└──────┘└──────┘└──────┘└──────┘ ↓ ↓ ↓ ┌──────────────────────────────────────┐ │ Humongous(大对象区) │ └──────────────────────────────────────┘G1配置:
-XX:+UseG1GC-XX:MaxGCPauseMillis=200-XX:G1HeapRegionSize=4-XX:InitiatingHeapOccupancyPercent=45五、JVM调优实战
5.1 常用JVM参数
# 堆大小设置-Xms4g# 初始堆大小-Xmx4g# 最大堆大小-Xmn2g# 新生代大小-Xss256k# 线程栈大小# 元空间设置-XX:MetaspaceSize=256m-XX:MaxMetaspaceSize=512m# GC日志-Xlog:gc*:file=gc.log5.2 典型GC问题场景
场景1:频繁Young GC
现象:对象分配频繁,Survivor区不够 原因:对象朝生夕灭太多 解决: -Xmn1024m 调大新生代 -XX:MaxTenuringThreshold=15 调大年龄阈值场景2:Full GC频繁
现象:老年代频繁回收 原因:大对象直接进老年代/内存泄漏 解决: -XX:+UseG1GC 换成G1 -XX:NewRatio=2 调小老年代比例 检查代码是否有内存泄漏5.3 排查工具
# 1. jps 查看Java进程jps-l# 2. jstat 查看GC情况jstat-gcutil进程ID1000100# 3. jmap 生成堆转储jmap-dump:format=b,file=heap.hprof 进程ID# 4. MAT分析dump文件5.4 实际调优案例
案例:某服务频繁Full GC
# 原始配置-Xms2g-Xmx2g-XX:+UseParallelGC# 现象# Full GC每分钟2-3次,每次停顿3-5秒# 分析jstat-gcutil进程ID100010# 发现Old区占用95%以上# 优化配置-Xms4g-Xmx4g-Xmn1g-XX:+UseG1GC-XX:MaxGCPauseMillis=200-XX:InitiatingHeapOccupancyPercent=50# 效果# Full GC降到每10分钟1次,停顿<1秒六、CPU 100%排查
# 1. 找到CPU占用高的进程top# 2. 找到该进程中高CPU的线程top-Hp进程ID# 3. 将线程ID转为16进制printf"%x\n"线程ID# 4. 查看线程堆栈jstack 进程ID|grep16进制线程ID-A50总结
| 模块 | 核心知识点 |
|---|---|
| 内存模型 | 堆/栈/方法区/程序计数器 |
| 垃圾回收 | 可达性分析/引用类型/GC Roots |
| 垃圾回收器 | Serial/ParNew/Parallel/CMS/G1/ZGC |
| 调优参数 | -Xms/-Xmx/-Xmn/-XX:+UseG1GC |
| 排查工具 | jps/jstat/jmap/jstack/MAT |
