引言
JVM是Java程序运行的基础,理解JVM内存模型和垃圾回收机制对于编写高性能Java应用至关重要。本文将深入解析JVM的核心原理,帮助你掌握JVM调优的关键技术。
一、JVM内存结构
1.1 运行时数据区
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| ┌─────────────────────────────────────┐ │ 方法区 (Method Area) │ │ (元空间 Metaspace in JDK8+) │ ├─────────────────────────────────────┤ │ 堆 (Heap) │ │ ┌──────────────────────────┐ │ │ │ 新生代 (Young Gen) │ │ │ │ ┌────┬──────┬──────┐ │ │ │ │ │Eden│S0│S1 │ │ │ │ │ └────┴──────┴──────┘ │ │ │ ├──────────────────────────┤ │ │ │ 老年代 (Old Gen) │ │ │ └──────────────────────────┘ │ └─────────────────────────────────────┘
线程私有: - 程序计数器 (PC Register) - Java虚拟机栈 (JVM Stack) - 本地方法栈 (Native Method Stack)
|
1.2 各区域详解
1. 堆(Heap)
1 2 3
|
Object obj = new Object();
|
堆的分代结构:
1 2 3 4 5 6 7
| 新生代 (Young Generation) ├── Eden区 (80%) ├── Survivor0 (10%) └── Survivor1 (10%)
老年代 (Old Generation) └── 存放长期存活的对象
|
2. 方法区(Method Area)
1 2 3 4 5 6 7 8 9
| public class MyClass { private static final String CONSTANT = "常量"; private static int count = 0; public void method() { } }
|
3. 虚拟机栈(JVM Stack)
1 2 3 4 5 6 7 8
| public void method() { int a = 1; int b = 2; int c = a + b; }
|
4. 程序计数器(PC Register)
1.3 对象的创建过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| Person p = new Person("Alice", 25);
|
1.4 对象的内存布局
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 对象在内存中的布局:
┌────────────────────┐ │ 对象头 (Header) │ │ ┌──────────────┐ │ │ │ Mark Word │ │ - 哈希码、GC年龄、锁信息 │ ├──────────────┤ │ │ │ Class Pointer│ │ - 指向类元数据 │ └──────────────┘ │ ├────────────────────┤ │ 实例数据 (Data) │ - 对象的字段值 ├────────────────────┤ │ 对齐填充 (Padding) │ - 补齐到8字节的倍数 └────────────────────┘
|
二、垃圾回收基础
2.1 如何判断对象可回收?
方法1:引用计数法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
class A { B b; } class B { A a; }
A a = new A(); B b = new B(); a.b = b; b.a = a;
|
方法2:可达性分析算法(JVM采用)
1 2 3 4 5 6 7 8
|
GC Roots包括: - 虚拟机栈中引用的对象 - 方法区中静态属性引用的对象 - 方法区中常量引用的对象 - 本地方法栈中引用的对象
|
1 2 3 4 5 6 7
| GC Roots / | \ A B C / \ | D E F G (不可达,可回收)
|
2.2 引用类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| Object obj = new Object();
SoftReference<Object> soft = new SoftReference<>(new Object());
WeakReference<Object> weak = new WeakReference<>(new Object());
PhantomReference<Object> phantom = new PhantomReference<>(new Object(), queue);
|
三、垃圾回收算法
3.1 标记-清除算法(Mark-Sweep)
1 2 3 4 5 6 7 8 9 10 11 12 13
| 标记阶段:标记所有需要回收的对象 清除阶段:回收被标记的对象
优点:简单 缺点:产生内存碎片
┌─┬─┬─┬─┬─┬─┬─┬─┐ │█│ │█│ │ │█│ │ │ 标记 └─┴─┴─┴─┴─┴─┴─┴─┘
┌─┬─┬─┬─┬─┬─┬─┬─┐ │█│□│█│□│□│█│□│□│ 清除后(有碎片) └─┴─┴─┴─┴─┴─┴─┴─┘
|
3.2 标记-整理算法(Mark-Compact)
1 2 3 4 5 6 7 8 9 10 11 12 13
| 标记阶段:标记存活对象 整理阶段:将存活对象移到一端
优点:无内存碎片 缺点:需要移动对象,效率较低
┌─┬─┬─┬─┬─┬─┬─┬─┐ │█│ │█│ │ │█│ │ │ 标记 └─┴─┴─┴─┴─┴─┴─┴─┘
┌─┬─┬─┬─┬─┬─┬─┬─┐ │█│█│█│□│□│□│□│□│ 整理后(无碎片) └─┴─┴─┴─┴─┴─┴─┴─┘
|
3.3 复制算法(Copying)
1 2 3 4 5 6 7 8 9 10
| 将内存分为两块,每次只使用一块 GC时将存活对象复制到另一块,清空当前块
优点:无碎片,效率高 缺点:浪费一半内存
From区 To区 ┌─┬─┬─┬─┐ ┌─┬─┬─┬─┐ │█│ │█│ │ ──复制──>│█│█│□│□│ └─┴─┴─┴─┘ └─┴─┴─┴─┘
|
3.4 分代收集算法
四、垃圾收集器
4.1 Serial收集器
4.2 ParNew收集器
4.3 Parallel Scavenge收集器
1 2 3 4 5 6 7
|
-XX:+UseParallelGC -XX:MaxGCPauseMillis=100 -XX:GCTimeRatio=99
|
4.4 CMS收集器(Concurrent Mark Sweep)
1 2 3 4 5 6 7 8 9 10 11
|
-XX:+UseConcMarkSweepGC
|
CMS的缺点:
4.5 G1收集器(Garbage First)
1 2 3 4 5 6 7
|
-XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:G1HeapRegionSize=n
|
G1特点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 堆被划分为多个Region (1-32MB)
┌────┬────┬────┬────┐ │Eden│Eden│Surv│Old │ ├────┼────┼────┼────┤ │Old │Eden│Old │Eden│ ├────┼────┼────┼────┤ │Huge│Surv│Old │Free│ └────┴────┴────┴────┘
- 并行与并发 - 分代收集 - 空间整合 - 可预测的停顿时间
|
4.6 ZGC收集器
特点:
- 染色指针技术
- 并发标记、并发转移
- Region动态创建销毁
五、GC日志分析
5.1 开启GC日志
1 2 3 4 5 6 7
| -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log
-Xlog:gc*:file=gc.log:time,level,tags
|
5.2 GC日志示例
1 2 3 4 5 6 7 8 9 10 11 12 13
| [GC (Allocation Failure) [PSYoungGen: 65536K->10752K(76288K)] 65536K->14856K(251392K), 0.0234567 secs] [Times: user=0.05 sys=0.01, real=0.02 secs]
解释: - PSYoungGen: 新生代收集器 - 65536K->10752K: 新生代从65MB减少到10MB - (76288K): 新生代总大小 - 65536K->14856K: 整个堆从65MB减少到14MB - (251392K): 堆总大小 - 0.0234567 secs: GC耗时
|
六、JVM调优实战
6.1 常用JVM参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| -Xms2g -Xmx2g -Xmn512m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m
-XX:+UseG1GC -XX:MaxGCPauseMillis=200
-Xlog:gc*:file=gc.log:time
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dump
-Xss256k
|
6.2 调优案例:解决Full GC频繁
问题现象:
分析步骤:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| [Full GC (Allocation Failure) 2048M->2047M(2048M), 3.5 secs]
jmap -dump:live,format=b,file=heap.hprof <pid>
|
6.3 调优案例:降低GC停顿时间
1 2 3 4 5 6 7 8 9 10 11 12 13
|
-XX:+UseConcMarkSweepGC -Xms4g -Xmx4g
-XX:+UseG1GC -XX:MaxGCPauseMillis=200 -Xms4g -Xmx4g -XX:G1HeapRegionSize=16m
|
6.4 内存泄漏排查
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
public class MemoryLeak { private static List<Object> list = new ArrayList<>(); public void add() { list.add(new Object()); } }
button.addListener(new Listener() { public void onClick() { } });
Connection conn = getConnection();
conn.close();
ThreadLocal<Object> local = new ThreadLocal<>(); local.set(new Object());
|
七、性能监控工具
7.1 命令行工具
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| jps -l
jstat -gc <pid> 1000
jinfo -flags <pid>
jmap -dump:live,format=b,file=heap.hprof <pid>
jstack <pid> > thread.txt
jcmd <pid> GC.heap_info
|
7.2 可视化工具
1 2 3 4 5 6 7 8 9 10 11
| jconsole
jvisualvm
|
八、面试高频问题
8.1 JVM内存结构?
答:JVM内存分为以下几个区域:
- 堆:存储对象实例,分为新生代和老年代
- 方法区:存储类信息、常量、静态变量
- 虚拟机栈:存储局部变量、方法调用
- 本地方法栈:本地方法调用
- 程序计数器:记录字节码行号
8.2 什么时候会发生Full GC?
答:以下情况会触发Full GC:
- 老年代空间不足
- 方法区空间不足
- CMS收集器的并发模式失败
- 显式调用System.gc()
- 新生代晋升到老年代时老年代空间不足
8.3 如何选择垃圾收集器?
1 2 3
| 吞吐量优先:Parallel Scavenge + Parallel Old 响应时间优先:CMS 或 G1 低延迟:ZGC 或 Shenandoah
|
总结
JVM调优的核心要点:
- 理解内存结构:堆、栈、方法区
- 掌握GC算法:标记-清除、标记-整理、复制
- 熟悉垃圾收集器:Serial、Parallel、CMS、G1、ZGC
- 会分析GC日志:找出性能瓶颈
- 掌握调优工具:jstat、jmap、MAT等
- 实战经验:解决实际问题
参考资料