Skip to content

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整堆整理 + SATBRegion 化,可预测停顿
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

调优思路

  1. 明确目标:吞吐量优先 / 延迟优先 / 内存占用优先
  2. 采集基线:记录无修改时的 GC 频率、停顿时间、堆占用
  3. 定位瓶颈:Young GC 频繁 / Full GC 频繁 / 老年代膨胀 / Metaspace OOM
  4. 小步调整:每次只改一个参数,观察 24 小时
  5. 回归验证:压测或线上灰度,确认效果