03-JVM调优(一)
本讲聚焦 JVM 内存模型、垃圾回收算法与常见调优参数,奠定生产环境调优基础。
JVM 内存区域
运行时数据区划分
JVM 运行时数据区分为线程私有与线程共享两类:
text
线程私有(每个线程一份)
├── 程序计数器 (Program Counter Register)
├── 虚拟机栈 (VM Stack)
└── 本地方法栈 (Native Method Stack)
线程共享(所有线程共用)
├── 堆 (Heap)
└── 方法区 (Method Area / Metaspace)各区域说明
| 区域 | 作用 | 异常 |
|---|---|---|
| 程序计数器 | 当前线程执行字节码行号 | 不会 OOM |
| 虚拟机栈 | 方法调用栈帧(局部变量、操作数栈) | StackOverflowError / OOM |
| 本地方法栈 | Native 方法调用 | StackOverflowError / OOM |
| 堆 | 对象实例、数组 | OutOfMemoryError: Java heap space |
| 方法区 | 类元数据、常量池、静态变量 | OOM: Metaspace |
JVM 1.8 元空间变化
JDK 1.8 后移除永久代,改为使用本地内存的 Metaspace:
text
JDK 1.7: 堆 = 年轻代 + 老年代 + 永久代
JDK 1.8: 堆 = 年轻代 + 老年代
Metaspace 位于本地内存(堆外)好处:避免永久代 OOM、GC 性能提升、可动态扩展本地内存。
对象内存布局
对象在堆中由三部分组成:
text
|------------------|
| 对象头 (Header) | 12 字节(MarkWord 8 + Klass Pointer 4,开启压缩指针)
|------------------|
| 实例数据 (Data) | 4 字节对齐
|------------------|
| 对齐填充 (Padding)| 不足 8 字节倍数时补齐
|------------------|java
// 使用 jol 查看对象布局
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.16</version>
</dependency>
Object o = new Object();
System.out.println(ClassLayout.parseInstance(o).toPrintable());输出示例:
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000005
8 4 (object header: class) 0xf80001f5
12 4 (alignment padding)
Instance size: 16 bytes垃圾回收基础
判断对象存活
1. 引用计数法(已废弃)
对象引用计数为 0 时回收。无法解决循环引用。
2. 可达性分析算法
从 GC Roots 出发,不可达的对象视为垃圾。
GC Roots 包括:
- 虚拟机栈中的局部变量
- 方法区中的静态变量
- 方法区中的常量
- 本地方法栈中的 JNI 引用
- Java 虚拟机内部引用(Class、异常对象、ClassLoader)
引用类型
- 强引用:
Object o = new Object(),永远不会回收 - 软引用:
SoftReference,内存不足时回收 - 弱引用:
WeakReference,下次 GC 时回收 - 虚引用:
PhantomReference,跟踪对象回收过程
垃圾回收算法
1. 标记-清除(Mark-Sweep)
- 标记所有存活对象,清除未标记对象
- 缺点:产生内存碎片
2. 标记-复制(Copying)
- 将内存分为两块,每次只用一块,GC 时复制存活对象到另一块
- 缺点:内存利用率低(50%)
- 适用:年轻代(朝生夕灭,存活率低)
3. 标记-整理(Mark-Compact)
- 标记后让存活对象向一端移动
- 缺点:移动成本高
- 适用:老年代
4. 分代收集
- 新生代:复制算法(Eden + 2 个 Survivor,比例 8:1:1)
- 老年代:标记-清除或标记-整理
text
新生代 (Young) 老年代 (Old)
+-----+----+----+ +-----------+
| Eden| S0 | S1 | ────► | |
+-----+----+----+ 晋升 +-----------+
8 1 1 内存比例大常见垃圾收集器
| 收集器 | 区域 | 算法 | 特点 |
|---|---|---|---|
| Serial / Serial Old | 新生代 / 老年代 | 复制 / 整理 | 单线程,STW |
| ParNew | 新生代 | 复制 | 多线程,配合 CMS |
| Parallel Scavenge | 新生代 | 复制 | 关注吞吐量 |
| Parallel Old | 老年代 | 整理 | 关注吞吐量 |
| CMS | 老年代 | 标记-清除 | 低延迟,4 阶段并发 |
| G1 | 整堆 | 整理 + SATB | Region 化,可预测停顿 |
| ZGC | 整堆 | 染色指针 | < 10ms 停顿,TB 级堆 |
G1 收集器要点
text
+------+------+------+------+
| E | E | O | O | Region 默认 1-32MB
+------+------+------+------+
| S | O | H | H | H = Humongous(大对象区)
+------+------+------+------+特点:
- 将堆分为多个 Region,逻辑分代而非物理分代
- 用户可设
-XX:MaxGCPauseMillis目标停顿时间 - 使用 SATB(Snapshot-At-The-Beginning)减少停顿
- Major GC 时迁移 Region,天然碎片整理
JVM 常用调优参数
堆内存参数
text
-Xms2g 初始堆大小
-Xmx4g 最大堆大小
-Xmn1g 新生代大小
-Xss512k 线程栈大小
-XX:MetaspaceSize=256m
-XX:MaxMetaspaceSize=512m收集器选择
text
-XX:+UseG1GC 使用 G1
-XX:+UseConcMarkSweepGC 使用 CMS(已废弃)
-XX:+UseZGC 使用 ZGC
-XX:MaxGCPauseMillis=200 目标停顿时间
-XX:G1HeapRegionSize=16m G1 Region 大小GC 日志
text
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+PrintGCTimeStamps
-Xlog:gc*=info:file=gc.log:time,uptime:filecount=10,filesize=100m生产推荐配置(中等规模应用)
text
-server
-Xms4g -Xmx4g
-Xmn2g
-XX:MetaspaceSize=256m
-XX:MaxMetaspaceSize=512m
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:+ParallelRefProcEnabled
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/data/dump/
-Xlog:gc*=info:file=/data/logs/gc.log:time,uptime:filecount=10,filesize=100m调优思路
- 明确目标:吞吐量优先 / 延迟优先 / 内存占用优先
- 采集基线:记录无修改时的 GC 频率、停顿时间、堆占用
- 定位瓶颈:Young GC 频繁 / Full GC 频繁 / 老年代膨胀 / Metaspace OOM
- 小步调整:每次只改一个参数,观察 24 小时
- 回归验证:压测或线上灰度,确认效果