JVM原理深度解析与调优实战
JVM(Java Virtual Machine)是Java程序的运行基石,深入理解JVM原理对于后端开发至关重要。本文将全面剖析JVM的核心机制,帮助读者掌握笔试面试重点,并提供实战调优经验。
1. JVM内存模型深度解析
1.1 内存区域划分
JVM将内存划分为以下几个运行时数据区域:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 
 | JVM内存结构├── 线程共享区域
 │   ├── 堆(Heap)
 │   │   ├── 新生代(Young Generation)
 │   │   │   ├── Eden区
 │   │   │   ├── Survivor0区
 │   │   │   └── Survivor1区
 │   │   └── 老年代(Old Generation)
 │   ├── 方法区(Method Area)
 │   │   ├── 类信息
 │   │   ├── 常量池
 │   │   ├── 静态变量
 │   │   └── JIT代码缓存
 │   └── 运行时常量池(Runtime Constant Pool)
 └── 线程私有区域
 ├── 程序计数器(Program Counter Register)
 ├── 虚拟机栈(JVM Stack)
 └── 本地方法栈(Native Method Stack)
 
 | 
1.2 各区域详解与OOM分析
1.2.1 堆(Heap)
堆是JVM管理的最大一块内存区域,用于存放对象实例:
| 12
 3
 4
 5
 6
 
 | -Xms512m    # 初始堆大小
 -Xmx1024m   # 最大堆大小
 -Xmn256m    # 新生代大小
 -XX:NewRatio=2    # 新生代与老年代比例
 -XX:SurvivorRatio=8    # Eden与Survivor比例
 
 | 
堆OOM示例:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 
 | public class HeapOOM {
 static class OOMObject {}
 
 public static void main(String[] args) {
 List<OOMObject> list = new ArrayList<>();
 while (true) {
 list.add(new OOMObject());
 }
 }
 }
 
 
 
 
 | 
1.2.2 方法区(Method Area)
方法区用于存储已被虚拟机加载的类信息、常量、静态变量等:
| 12
 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
 29
 30
 
 | public class MethodAreaOOM extends ClassLoader {
 public static void main(String[] args) {
 MethodAreaOOM loader = new MethodAreaOOM();
 int i = 0;
 try {
 while (true) {
 Enhancer enhancer = new Enhancer();
 enhancer.setSuperclass(OOMObject.class);
 enhancer.setUseCache(false);
 enhancer.setCallback(new MethodInterceptor() {
 @Override
 public Object intercept(Object obj, Method method,
 Object[] args, MethodProxy proxy) throws Throwable {
 return proxy.invokeSuper(obj, args);
 }
 });
 enhancer.create();
 i++;
 }
 } catch (Exception e) {
 System.out.println("创建类数量:" + i);
 e.printStackTrace();
 }
 }
 
 static class OOMObject {}
 }
 
 
 
 | 
1.2.3 虚拟机栈(JVM Stack)
每个方法执行时都会创建一个栈帧用于存储局部变量表、操作数栈等:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 
 | public class StackOOM {
 private int stackLength = 1;
 
 public void stackLeak() {
 stackLength++;
 stackLeak();
 }
 
 public static void main(String[] args) {
 StackOOM oom = new StackOOM();
 try {
 oom.stackLeak();
 } catch (Throwable e) {
 System.out.println("栈深度:" + oom.stackLength);
 e.printStackTrace();
 }
 }
 }
 
 
 
 
 | 
1.3 对象分配与内存布局
1.3.1 对象创建过程
| 12
 
 | Object obj = new Object();
 
 | 
- 类加载检查:检查类是否已加载、解析、初始化
- 分配内存:
- 指针碰撞(Bump the Pointer):内存规整时使用
- 空闲列表(Free List):内存碎片较多时使用
 
- 初始化零值:将分配到的内存空间初始化为零值
- 设置对象头:包括哈希码、GC分代年龄、锁状态等
- 执行init方法:按照程序员的意愿初始化对象
1.3.2 对象内存布局
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | 对象内存布局├── 对象头(Header)
 │   ├── Mark Word(64位系统占8字节)
 │   │   ├── 哈希码(25位)
 │   │   ├── GC分代年龄(4位)
 │   │   ├── 锁状态标志(2位)
 │   │   ├── 是否偏向锁(1位)
 │   │   └── 偏向线程ID(23位)
 │   └── 类型指针(Class Pointer)
 ├── 实例数据(Instance Data)
 └── 对齐填充(Padding)
 
 | 
2. 垃圾回收机制深度剖析
2.1 垃圾回收基础理论
2.1.1 判断对象存活算法
引用计数法:
- 原理:给对象添加引用计数器,引用时+1,失效时-1
- 缺点:无法解决循环引用问题
可达性分析算法:
- 原理:从GC Roots开始向下搜索,不可达的对象可被回收
- GC Roots包括:
- 虚拟机栈中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI引用的对象
 
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 
 | public class ReferenceCountingGC {
 public Object instance = null;
 
 public static void testGC() {
 ReferenceCountingGC objA = new ReferenceCountingGC();
 ReferenceCountingGC objB = new ReferenceCountingGC();
 
 objA.instance = objB;
 objB.instance = objA;
 
 objA = null;
 objB = null;
 
 System.gc();
 }
 }
 
 | 
2.1.2 垃圾回收算法
标记-清除算法(Mark-Sweep):
- 标记所有需要回收的对象,然后统一回收
- 缺点:效率低、产生内存碎片
复制算法(Copying):
- 将内存分为两块,每次使用一块,回收时复制存活对象到另一块
- 优点:实现简单、运行高效
- 缺点:内存利用率低
标记-整理算法(Mark-Compact):
- 标记存活对象,然后将所有存活对象向一端移动,清理边界外内存
- 适用于老年代
分代收集算法:
- 新生代:复制算法
- 老年代:标记-清除或标记-整理算法
2.2 HotSpot垃圾回收器详解
2.2.1 垃圾回收器概览
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 
 | 垃圾回收器分类├── 新生代收集器
 │   ├── Serial收集器(-XX:+UseSerialGC)
 │   ├── ParNew收集器(-XX:+UseParNewGC)
 │   └── Parallel Scavenge(-XX:+UseParallelGC)
 ├── 老年代收集器
 │   ├── Serial Old收集器
 │   ├── Parallel Old收集器(-XX:+UseParallelOldGC)
 │   └── CMS收集器(-XX:+UseConcMarkSweepGC)
 └── 整堆收集器
 ├── G1收集器(-XX:+UseG1GC)
 ├── ZGC(JDK11+)
 └── Shenandoah(JDK12+)
 
 | 
2.2.2 CMS收集器详解
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器:
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | CMS收集器工作流程├── 初始标记(Initial Mark)
 │   └── 标记GC Roots能直接关联的对象,需要"Stop The World"
 ├── 并发标记(Concurrent Mark)
 │   └── 进行GC Roots Tracing,与应用线程并发执行
 ├── 重新标记(Remark)
 │   └── 修正并发标记期间变动的标记记录,需要"Stop The World"
 └── 并发清除(Concurrent Sweep)
 └── 清除无用对象,与应用线程并发执行
 
 | 
CMS优缺点:
- 优点:并发收集、低停顿
- 缺点:
- 对CPU资源敏感
- 无法处理浮动垃圾
- 基于标记-清除算法,会产生内存碎片
 
2.2.3 G1收集器详解
G1(Garbage First)收集器是一款面向服务端应用的垃圾收集器:
| 12
 3
 4
 5
 6
 
 | G1收集器特点├── 并行与并发
 ├── 分代收集
 ├── 空间整合(整体基于标记-整理,局部基于复制)
 ├── 可预测的停顿时间模型
 └── 将整个Java堆划分为多个大小相等的独立区域(Region)
 
 | 
G1收集器工作流程:
- 初始标记(Initial Marking)
- 并发标记(Concurrent Marking)
- 最终标记(Final Marking)
- 筛选回收(Live Data Counting and Evacuation)
2.2.4 垃圾回收器对比
| 收集器 | 串行/并行/并发 | 算法 | 目标 | 适用场景 | 
| Serial | 串行 | 复制算法 | 响应速度优先 | 单CPU环境,Client模式 | 
| ParNew | 并行 | 复制算法 | 响应速度优先 | 多CPU环境,配合CMS | 
| Parallel | 并行 | 复制算法 | 吞吐量优先 | 后台运算,不需要太多交互 | 
| CMS | 并发 | 标记-清除 | 响应速度优先 | 互联网站或B/S系统 | 
| G1 | 并发 | 标记-整理+复制 | 响应速度优先 | 面向服务端应用 | 
2.3 Full GC触发条件
| 12
 3
 4
 5
 6
 
 | 1. 老年代空间不足
 2. 方法区空间不足(JDK8前)
 3. 调用System.gc()
 4. CMS GC时出现promotion failed和concurrent mode failure
 5. 统计得到的Minor GC晋升到老年代的平均大小大于老年代的剩余空间
 
 | 
3. 类加载机制深度解析
3.1 类加载过程
| 12
 3
 4
 5
 6
 7
 8
 
 | 类加载生命周期├── 加载(Loading)
 ├── 验证(Verification)
 ├── 准备(Preparation)
 ├── 解析(Resolution)
 ├── 初始化(Initialization)
 ├── 使用(Using)
 └── 卸载(Unloading)
 
 | 
3.1.1 加载阶段
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 
 | 
 ClassLoader.getSystemClassLoader().loadClass("com.example.MyClass");
 
 
 URL url = new URL("http://example.com/classes/");
 URLClassLoader loader = new URLClassLoader(new URL[]{url});
 Class<?> clazz = loader.loadClass("com.example.NetworkClass");
 
 
 byte[] classBytes = generateClassBytes();
 Class<?> dynamicClass = defineClass("DynamicClass", classBytes, 0, classBytes.length);
 
 | 
3.1.2 验证阶段
- 文件格式验证:验证字节流是否符合Class文件格式规范
- 元数据验证:对字节码描述的信息进行语义分析
- 字节码验证:通过数据流和控制流分析,确保程序语义是合法的
- 符号引用验证:确保解析动作能正常执行
3.1.3 准备阶段
为类变量分配内存并设置类变量初始值:
| 12
 3
 4
 5
 6
 7
 
 | public class PreparationTest {
 public static int value = 123;
 
 
 public static final String str = "hello";
 }
 
 | 
3.1.4 解析阶段
将常量池内的符号引用替换为直接引用:
- 类或接口的解析
- 字段解析
- 类方法解析
- 接口方法解析
3.1.5 初始化阶段
执行类构造器<clinit>()方法:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | public class InitializationTest {static {
 System.out.println("InitializationTest类初始化");
 }
 
 public static int value = 123;
 
 public static void main(String[] args) {
 System.out.println(InitializationTest.value);
 }
 }
 
 | 
3.2 双亲委派模型
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | 类加载器层次结构├── 启动类加载器(Bootstrap ClassLoader)
 │   └── 加载<JAVA_HOME>/lib目录下的类
 ├── 扩展类加载器(Extension ClassLoader)
 │   └── 加载<JAVA_HOME>/lib/ext目录下的类
 ├── 应用程序类加载器(Application ClassLoader)
 │   └── 加载用户类路径(ClassPath)上的类
 └── 自定义类加载器(User Defined ClassLoader)
 └── 用户自定义的类加载器
 
 | 
3.2.1 双亲委派模型实现
| 12
 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
 29
 30
 31
 32
 33
 34
 35
 36
 37
 
 | protected Class<?> loadClass(String name, boolean resolve)
 throws ClassNotFoundException {
 synchronized (getClassLoadingLock(name)) {
 
 Class<?> c = findLoadedClass(name);
 if (c == null) {
 long t0 = System.nanoTime();
 try {
 if (parent != null) {
 
 c = parent.loadClass(name, false);
 } else {
 
 c = findBootstrapClassOrNull(name);
 }
 } catch (ClassNotFoundException e) {
 
 }
 
 if (c == null) {
 
 long t1 = System.nanoTime();
 c = findClass(name);
 
 
 sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
 sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
 sun.misc.PerfCounter.getFindClasses().increment();
 }
 }
 if (resolve) {
 resolveClass(c);
 }
 return c;
 }
 }
 
 | 
3.2.2 破坏双亲委派模型
第一次破坏:JDK1.2引入双亲委派模型前,用户自定义类加载器已有loadClass方法
第二次破坏:JNDI、JDBC等服务提供者接口(SPI)需要调用启动类加载器无法加载的代码
第三次破坏:OSGi为了实现模块化热部署,每个模块都有自己的类加载器
3.3 类加载优化策略
3.3.1 预加载策略
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 
 | public class PreloadClasses {
 static {
 try {
 Class.forName("java.util.concurrent.ConcurrentHashMap");
 Class.forName("java.util.ArrayList");
 Class.forName("java.util.HashMap");
 } catch (ClassNotFoundException e) {
 e.printStackTrace();
 }
 }
 }
 
 | 
3.3.2 懒加载策略
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 
 | public class LazySingleton {
 private LazySingleton() {}
 
 private static class Holder {
 static final LazySingleton INSTANCE = new LazySingleton();
 }
 
 public static LazySingleton getInstance() {
 return Holder.INSTANCE;
 }
 }
 
 | 
3.3.3 热部署实现
| 12
 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
 29
 30
 31
 32
 33
 34
 
 | public class HotDeployClassLoader extends URLClassLoader {
 public HotDeployClassLoader(URL[] urls) {
 super(urls, null);
 }
 
 @Override
 protected Class<?> findClass(String name) throws ClassNotFoundException {
 try {
 byte[] classBytes = loadClassBytes(name);
 return defineClass(name, classBytes, 0, classBytes.length);
 } catch (IOException e) {
 throw new ClassNotFoundException("Class " + name + " not found", e);
 }
 }
 
 private byte[] loadClassBytes(String className) throws IOException {
 String classFile = className.replace('.', '/') + ".class";
 URL url = findResource(classFile);
 if (url == null) {
 throw new IOException("Class file not found: " + classFile);
 }
 
 try (InputStream in = url.openStream()) {
 ByteArrayOutputStream buffer = new ByteArrayOutputStream();
 int data = in.read();
 while (data != -1) {
 buffer.write(data);
 data = in.read();
 }
 return buffer.toByteArray();
 }
 }
 }
 
 | 
4. 对象创建与内存分配策略
4.1 对象创建详细过程
4.1.1 检查加载
首先检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并检查这个符号引用代表的类是否已被加载、解析和初始化过。
4.1.2 分配内存
4.1.3 内存分配实战
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 
 | -Xms20m -Xmx20m -Xmn10m -XX:+PrintGCDetails -XX:SurvivorRatio=8
 
 
 public class ObjectAllocationTest {
 private static final int _1MB = 1024 * 1024;
 
 public static void main(String[] args) {
 byte[] allocation1, allocation2, allocation3, allocation4;
 
 allocation1 = new byte[2 * _1MB];
 allocation2 = new byte[2 * _1MB];
 allocation3 = new byte[2 * _1MB];
 allocation4 = new byte[4 * _1MB];
 }
 }
 
 
 [GC [DefNew: 6487K->148K(9216K), 0.0038720 secs] 6487K->6292K(19456K), 0.0039180 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
 
 | 
4.2 内存分配策略
4.2.1 对象优先在Eden分配
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 
 | public class EdenAllocation {
 private static final int _1MB = 1024 * 1024;
 
 public static void testAllocation() {
 byte[] allocation1, allocation2, allocation3, allocation4;
 allocation1 = new byte[2 * _1MB];
 allocation2 = new byte[2 * _1MB];
 allocation3 = new byte[2 * _1MB];
 allocation4 = new byte[4 * _1MB];
 }
 
 public static void main(String[] args) {
 testAllocation();
 }
 }
 
 | 
4.2.2 大对象直接进入老年代
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | public class BigObjectToOld {
 private static final int _1MB = 1024 * 1024;
 
 public static void main(String[] args) {
 
 byte[] bigObject = new byte[8 * _1MB];
 }
 }
 
 
 | 
4.2.3 长期存活的对象进入老年代
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 
 | public class LongLifeToOld {
 private static final int _1MB = 1024 * 1024;
 
 public static void main(String[] args) {
 byte[] allocation1 = new byte[_1MB / 4];
 byte[] allocation2 = new byte[4 * _1MB];
 allocation2 = null;
 allocation2 = new byte[4 * _1MB];
 }
 }
 
 
 | 
5. JVM并发编程支持机制
5.1 synchronized锁升级过程
5.1.1 锁状态演化
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | synchronized锁升级过程├── 无锁状态
 ├── 偏向锁(Biased Locking)
 │   ├── 偏向第一个获取锁的线程
 │   └── 通过CAS记录线程ID
 ├── 轻量级锁(Lightweight Locking)
 │   ├── 线程竞争不激烈时使用
 │   └── 通过CAS操作获取锁
 └── 重量级锁(Heavyweight Locking)
 ├── 线程竞争激烈时使用
 └── 依赖操作系统Mutex Lock
 
 | 
5.1.2 锁升级实战
| 12
 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
 29
 30
 31
 32
 
 | public class LockEscalationDemo {
 public static void main(String[] args) throws InterruptedException {
 Object lock = new Object();
 
 
 synchronized (lock) {
 System.out.println("偏向锁状态");
 System.out.println(ClassLayout.parseInstance(lock).toPrintable());
 }
 
 
 Thread t1 = new Thread(() -> {
 synchronized (lock) {
 System.out.println("轻量级锁状态");
 System.out.println(ClassLayout.parseInstance(lock).toPrintable());
 }
 });
 
 Thread t2 = new Thread(() -> {
 synchronized (lock) {
 System.out.println("重量级锁状态");
 System.out.println(ClassLayout.parseInstance(lock).toPrintable());
 }
 });
 
 t1.start();
 t2.start();
 t1.join();
 t2.join();
 }
 }
 
 | 
5.2 volatile关键字原理
5.2.1 内存语义
- 可见性:写volatile变量时,JMM会把该线程对应的本地内存中的共享变量值立即刷新到主内存
- 有序性:读volatile变量时,JMM会把该线程对应的本地内存置为无效,从主内存中读取共享变量
5.2.2 volatile实现原理
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 
 | public class VolatileExample {
 private volatile boolean flag = false;
 
 public void writer() {
 flag = true;
 }
 
 public void reader() {
 if (flag) {
 System.out.println("flag is true");
 }
 }
 }
 
 
 
 
 
 | 
5.3 Monitor原理
5.3.1 Monitor结构
| 12
 3
 4
 5
 
 | Monitor结构├── Owner
 ├── EntryList
 ├── WaitSet
 └── Count
 
 | 
5.3.2 Monitor实现
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 
 | public class MonitorDemo {
 private final Object monitor = new Object();
 
 public void synchronizedMethod() {
 synchronized (monitor) {
 
 try {
 monitor.wait();
 monitor.notify();
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 }
 }
 }
 
 | 
6. JVM调优实战
6.1 调优工具
6.1.1 命令行工具
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 
 | jps -lvm
 
 
 jstat -gc 12345 1000 5
 
 
 jinfo -flags 12345
 
 
 jmap -histo 12345
 jmap -dump:format=b,file=heap.hprof 12345
 
 
 jstack 12345 > thread_dump.txt
 
 | 
6.1.2 可视化工具
- JConsole:JVM监控与管理控制台
- VisualVM:功能更强大的可视化工具
- MAT:内存分析工具
- GCEasy:在线GC日志分析工具
6.2 调优案例分析
6.2.1 高并发场景调优
| 12
 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
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 
 | 
 
 
 -Xms4g -Xmx4g -Xmn2g
 -XX:+UseG1GC
 -XX:MaxGCPauseMillis=200
 -XX:G1HeapRegionSize=16m
 -XX:+UnlockExperimentalVMOptions
 -XX:+UseCGroupMemoryLimitForHeap
 -XX:+PrintGCDetails
 -XX:+PrintGCTimeStamps
 -XX:+PrintGCApplicationStoppedTime
 -Xloggc:/var/log/app/gc.log
 
 
 public class HighConcurrencyService {
 
 private final ObjectPool<RequestContext> contextPool = new GenericObjectPool<>(
 new BasePooledObjectFactory<RequestContext>() {
 @Override
 public RequestContext create() {
 return new RequestContext();
 }
 
 @Override
 public PooledObject<RequestContext> wrap(RequestContext obj) {
 return new DefaultPooledObject<>(obj);
 }
 }
 );
 
 public void handleRequest() {
 RequestContext context = null;
 try {
 context = contextPool.borrowObject();
 
 } catch (Exception e) {
 
 } finally {
 if (context != null) {
 context.reset();
 contextPool.returnObject(context);
 }
 }
 }
 }
 
 | 
6.2.2 内存泄漏排查
| 12
 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
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 
 | public class MemoryLeakDetector {
 
 public static void main(String[] args) throws Exception {
 
 String pid = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
 
 
 Process process = Runtime.getRuntime().exec(
 "jmap -dump:format=b,file=heap.hprof " + pid
 );
 process.waitFor();
 
 
 System.out.println("堆转储文件已生成:heap.hprof");
 System.out.println("使用MAT打开文件,查找内存泄漏");
 }
 }
 
 
 public class CommonMemoryLeaks {
 
 
 private static final List<Object> staticList = new ArrayList<>();
 
 public void addToStaticList(Object obj) {
 staticList.add(obj);
 }
 
 
 public void resourceLeak() throws IOException {
 FileInputStream fis = new FileInputStream("file.txt");
 
 }
 
 
 public class InnerClass {
 
 }
 
 
 private static final ThreadLocal<byte[]> threadLocal = new ThreadLocal<>();
 
 public void threadLocalLeak() {
 threadLocal.set(new byte[1024 * 1024]);
 }
 }
 
 | 
6.3 性能调优参数总结
6.3.1 堆内存参数
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 
 | -Xms<size>
 -Xmx<size>
 -Xmn<size>
 -XX:NewRatio=<n>
 -XX:SurvivorRatio=<n>
 
 
 -XX:PretenureSizeThreshold=<size>
 -XX:MaxTenuringThreshold=<n>
 -XX:+UseTLAB
 -XX:TLABSize=<size>
 
 | 
6.3.2 GC参数
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 
 | -XX:+UseSerialGC
 
 
 -XX:+UseParNewGC
 -XX:ParNewGCThreads=<n>
 
 
 -XX:+UseParallelGC
 -XX:+UseParallelOldGC
 -XX:ParallelGCThreads=<n>
 -XX:MaxGCPauseMillis=<n>
 -XX:GCTimeRatio=<n>
 
 
 -XX:+UseConcMarkSweepGC
 -XX:CMSInitiatingOccupancyFraction=<n>
 -XX:+UseCMSCompactAtFullCollection
 -XX:CMSFullGCsBeforeCompaction=<n>
 
 
 -XX:+UseG1GC
 -XX:MaxGCPauseMillis=<n>
 -XX:G1HeapRegionSize=<size>
 -XX:G1NewSizePercent=<n>
 -XX:G1MaxNewSizePercent=<n>
 
 | 
6.3.3 监控参数
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | -XX:+PrintGC
 -XX:+PrintGCDetails
 -XX:+PrintGCTimeStamps
 -XX:+PrintGCApplicationStoppedTime
 -Xloggc:<filename>
 
 
 -XX:+HeapDumpOnOutOfMemoryError
 -XX:HeapDumpPath=<path>
 -XX:+UseGCOverheadLimit
 
 | 
7. 高频面试题总结
7.1 JVM内存模型相关
- JVM内存区域如何划分?各区域作用是什么? - 
- 方法区:存储类信息、常量、静态变量
- 堆:存放对象实例
- 虚拟机栈:方法调用、局部变量
- 本地方法栈:Native方法调用
- 程序计数器:当前线程执行的字节码行号指示器
 
- 堆内存为什么要分代? - 
- 基于弱代假说:绝大多数对象都是朝生夕灭的
- 提高垃圾回收效率
- 针对不同代采用不同的垃圾回收算法
 
- 方法区和元空间有什么区别? - 
- JDK8前:方法区在永久代(PermGen)
- JDK8后:方法区在元空间(Metaspace),使用本地内存
- 元空间可以动态扩容,避免OOM
 
7.2 垃圾回收相关
- 如何判断对象是否存活? - 
- 引用计数法:无法解决循环引用
- 可达性分析:从GC Roots开始,不可达的对象可被回收
 
- 垃圾回收算法有哪些? - 
- 标记-清除:简单但产生碎片
- 复制算法:高效但内存利用率低
- 标记-整理:避免碎片,适合老年代
- 分代收集:新生代复制,老年代标记-整理
 
- CMS和G1收集器的区别? - 
- CMS:标记-清除,老年代收集器,并发收集
- G1:标记-整理+复制,整堆收集器,可预测停顿时间
 
- Full GC触发条件有哪些? - 
- 老年代空间不足
- 方法区空间不足
- System.gc()调用
- CMS GC时出现promotion failed和concurrent mode failure
 
7.3 类加载机制相关
- 类加载过程是怎样的? 
- 什么是双亲委派模型? - 
- 类加载请求先委派给父类加载器
- 避免类的重复加载
- 保证Java核心库的安全性
 
- 如何打破双亲委派模型? - 
- SPI机制(JDBC、JNDI)
- OSGi模块化
- 自定义类加载器
 
7.4 并发编程相关
- synchronized锁升级过程? 
- volatile关键字的作用? 
- Monitor的实现原理? - 
- 基于ObjectMonitor实现
- 包含Owner、EntryList、WaitSet
 
8. 总结与最佳实践
8.1 JVM调优黄金法则
- 先监控再调优:使用JConsole、VisualVM等工具收集数据
- 小步快跑:每次只调整一个参数,观察效果
- 关注GC日志:分析GC频率、停顿时间、回收量
- 避免过度优化:80%的性能问题由20%的代码引起
8.2 生产环境配置模板
| 12
 3
 4
 5
 6
 7
 8
 
 | -Xms4g -Xmx4g
 -XX:+UseG1GC
 -XX:MaxGCPauseMillis=200
 -XX:+PrintGCDetails -XX:+PrintGCTimeStamps
 -Xloggc:/var/log/app/gc.log
 -XX:+HeapDumpOnOutOfMemoryError
 -XX:HeapDumpPath=/var/log/app/heap.hprof
 
 | 
8.3 学习建议
- 理论结合实践:通过实际调优案例加深理解
- 阅读源码:OpenJDK源码是最好的学习资料
- 关注社区:跟进JVM最新发展(ZGC、Shenandoah)
- 工具熟练:掌握各种JVM监控和调优工具
通过系统学习JVM原理,你将能够:
- 快速定位和解决生产环境问题
- 编写高性能的Java应用
- 在技术面试中脱颖而出
- 为架构设计提供坚实基础
参考资料
- 《深入理解Java虚拟机:JVM高级特性与最佳实践》
- 《Java性能优化权威指南》
- OpenJDK官方文档
- Oracle JVM调优指南
- 《Java并发编程实战》
本文档将持续更新,欢迎交流讨论!