34 JVM深入理解

目录

  • [🟠 34 JVM深入理解](#🟠 34 JVM深入理解)
    • [1. JVM概述](#1. JVM概述)
      • [1.1 什么是JVM](#1.1 什么是JVM)
      • [1.2 JVM架构](#1.2 JVM架构)
      • [1.3 主流JVM](#1.3 主流JVM)
    • [2. 内存模型](#2. 内存模型)
      • [2.1 运行时数据区](#2.1 运行时数据区)
      • [2.2 各区域详解](#2.2 各区域详解)
      • [2.3 堆内存细分](#2.3 堆内存细分)
      • [2.4 对象生命周期](#2.4 对象生命周期)
    • [3. 垃圾回收算法](#3. 垃圾回收算法)
      • [3.1 如何判断对象可回收](#3.1 如何判断对象可回收)
      • [3.2 回收算法对比](#3.2 回收算法对比)
      • [3.3 分代收集策略](#3.3 分代收集策略)
    • [4. 垃圾收集器](#4. 垃圾收集器)
      • [4.1 收集器一览](#4.1 收集器一览)
      • [4.2 G1收集器详解](#4.2 G1收集器详解)
      • [4.3 ZGC收集器](#4.3 ZGC收集器)
      • [4.4 如何选择收集器](#4.4 如何选择收集器)
    • [5. 类加载机制](#5. 类加载机制)
      • [5.1 类加载过程](#5.1 类加载过程)
      • [5.2 双亲委派模型](#5.2 双亲委派模型)
      • [5.3 打破双亲委派](#5.3 打破双亲委派)
    • [6. JVM调优参数](#6. JVM调优参数)
      • [6.1 内存参数](#6.1 内存参数)
      • [6.2 GC参数](#6.2 GC参数)
      • [6.3 常用调优组合](#6.3 常用调优组合)
    • [7. GC日志分析](#7. GC日志分析)
      • [7.1 GC日志格式](#7.1 GC日志格式)
      • [7.2 关键指标](#7.2 关键指标)
      • [7.3 分析工具](#7.3 分析工具)
    • [8. 实战:排查内存问题](#8. 实战:排查内存问题)
      • [8.1 常见问题](#8.1 常见问题)
      • [8.2 排查步骤](#8.2 排查步骤)
      • [8.3 内存泄漏示例](#8.3 内存泄漏示例)
    • [9. 总结](#9. 总结)
    • [📚 参考资料](#📚 参考资料)

🟠 34 JVM深入理解

📅 更新于 2026年6月 | ✍️ 原创文章,转载请注明出处



1. JVM概述

1.1 什么是JVM

JVM(Java Virtual Machine)是Java程序的运行环境,负责将字节码转换为机器码执行。

1.2 JVM架构

复制代码
┌─────────────────────────────────────────────────────────┐
│                    Java Source Code                       │
└─────────────────────────┬───────────────────────────────┘
                          ▼
┌─────────────────────────────────────────────────────────┐
│                   Java Compiler (javac)                  │
└─────────────────────────┬───────────────────────────────┘
                          ▼
┌─────────────────────────────────────────────────────────┐
│                   Bytecode (.class)                      │
└─────────────────────────┬───────────────────────────────┘
                          ▼
┌─────────────────────────────────────────────────────────┐
│                      JVM                                 │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐  │
│  │ Class Loader │  │   Runtime    │  │   Execution  │  │
│  │   Subsystem  │  │   Data Areas │  │   Engine     │  │
│  └──────────────┘  └──────────────┘  └──────────────┘  │
└─────────────────────────────────────────────────────────┘

1.3 主流JVM

JVM 厂商 特点
HotSpot Oracle 默认JVM,最广泛
OpenJ9 Eclipse 低内存占用
GraalVM Oracle 多语言支持,AOT编译
Zing Azul 低延迟,无停顿

2. 内存模型

2.1 运行时数据区

复制代码
┌─────────────────────────────────────────────────────────┐
│                      JVM内存                             │
│                                                         │
│  ┌─────────────────────────────────────────────────┐   │
│  │              线程私有                              │   │
│  │  ┌──────────┐ ┌──────────┐ ┌──────────────┐    │   │
│  │  │ 程序计数器│ │ 虚拟机栈  │ │ 本地方法栈    │    │   │
│  │  │   (PC)    │ │  (Stack)  │ │ (Native Stack)│    │   │
│  │  └──────────┘ └──────────┘ └──────────────┘    │   │
│  └─────────────────────────────────────────────────┘   │
│                                                         │
│  ┌─────────────────────────────────────────────────┐   │
│  │              线程共享                              │   │
│  │  ┌──────────────────┐  ┌──────────────────┐    │   │
│  │  │    堆 (Heap)      │  │  方法区/元空间    │    │   │
│  │  │   对象实例         │  │  类信息/常量池    │    │   │
│  │  │                  │  │                  │    │   │
│  │  │  ┌────┐ ┌────┐  │  │  (Metaspace)     │    │   │
│  │  │  │新生代│ │老年代│  │  │                  │    │   │
│  │  │  │Eden│ │Old │  │  │                  │    │   │
│  │  │  │S0  │ │    │  │  │                  │    │   │
│  │  │  │S1  │ │    │  │  │                  │    │   │
│  │  │  └────┘ └────┘  │  │                  │    │   │
│  │  └──────────────────┘  └──────────────────┘    │   │
│  └─────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────┘

2.2 各区域详解

区域 线程 存储内容 异常
程序计数器 私有 当前执行的字节码行号 无OOM
虚拟机栈 私有 栈帧(局部变量表、操作数栈) StackOverflowError / OOM
本地方法栈 私有 Native方法 StackOverflowError / OOM
共享 对象实例、数组 OOM
方法区/元空间 共享 类信息、常量、静态变量 OOM
直接内存 - NIO的DirectBuffer OOM

2.3 堆内存细分

复制代码
堆内存 (Heap)
├── 新生代 (Young Generation) - 1/3 堆空间
│   ├── Eden区 - 8/10 新生代
│   ├── Survivor0 (S0) - 1/10 新生代
│   └── Survivor1 (S1) - 1/10 新生代
│
└── 老年代 (Old Generation) - 2/3 堆空间

2.4 对象生命周期

复制代码
1. new → Eden区
2. Eden满 → Minor GC → 存活对象 → S0
3. S0满 → Minor GC → 存活对象 → S1
4. 年龄达到阈值(默认15) → 老年代
5. 老年代满 → Major GC / Full GC

3. 垃圾回收算法

3.1 如何判断对象可回收

① 引用计数法

java 复制代码
// 每个对象维护引用计数
Object a = new Object();  // 计数=1
Object b = a;             // 计数=2
a = null;                 // 计数=1
b = null;                 // 计数=0 → 可回收

❌ 问题:循环引用无法回收

② 可达性分析(JVM使用)

复制代码
GC Roots
    │
    ├── 虚拟机栈中引用的对象
    ├── 方法区中静态属性引用的对象
    ├── 方法区中常量引用的对象
    ├── 本地方法栈中JNI引用的对象
    └── 所有被同步锁持有的对象

从GC Roots出发,不可达的对象 → 可回收

3.2 回收算法对比

算法 原理 优点 缺点
标记-清除 标记存活,清除未标记 简单 内存碎片
标记-整理 标记存活,向一端移动 无碎片 移动开销大
复制算法 分两块,存活对象复制到另一块 无碎片,快 空间浪费50%
分代收集 新生代用复制,老年代用标记-整理 综合最优 实现复杂

3.3 分代收集策略

区域 算法 原因
Eden → S0/S1 复制 新生代对象朝生夕死,存活率低
老年代 标记-整理 对象存活率高,复制浪费空间

4. 垃圾收集器

4.1 收集器一览

收集器 区域 算法 特点
Serial 新生代 复制 单线程,简单高效
ParNew 新生代 复制 Serial的多线程版本
Parallel Scavenge 新生代 复制 吞吐量优先
Serial Old 老年代 标记-整理 单线程
CMS 老年代 标记-清除 低延迟(已废弃)
Parallel Old 老年代 标记-整理 吞吐量优先
G1 全堆 分区 JDK9默认,平衡吞吐和延迟
ZGC 全堆 着色指针 超低延迟(<10ms)
Shenandoah 全堆 转发指针 超低延迟

4.2 G1收集器详解

复制代码
G1将堆划分为多个大小相等的Region(1-32MB)

┌─────┬─────┬─────┬─────┬─────┬─────┐
│ E   │ S   │ O   │ O   │ H   │ E   │
│     │     │     │     │     │     │
├─────┼─────┼─────┼─────┼─────┼─────┤
│ O   │ E   │ H   │ O   │ E   │ S   │
│     │     │     │     │     │     │
├─────┼─────┼─────┼─────┼─────┼─────┤
│ E   │ O   │ O   │ E   │ O   │ E   │
└─────┴─────┴─────┴─────┴─────┴─────┘

E=Eden  S=Survivor  O=Old  H=Humongous(大对象)

G1特点

  • 可预测的停顿时间(-XX:MaxGCPauseMillis=200
  • 优先回收垃圾最多的Region(Garbage First)
  • JDK9+默认收集器

4.3 ZGC收集器

复制代码
ZGC目标:停顿时间 < 10ms,不随堆大小增长

特点:
- 着色指针(Colored Pointers)
- 读屏障(Load Barrier)
- 并发执行几乎所有阶段
- 支持TB级堆内存
- JDK15+正式可用

启用:-XX:+UseZGC

4.4 如何选择收集器

场景 推荐收集器
小堆(<4GB) Serial / Parallel
中等堆,吞吐量优先 Parallel + Parallel Old
中等堆,延迟敏感 G1
大堆(>8GB),低延迟 ZGC
超大堆(>100GB) ZGC / Shenandoah

5. 类加载机制

5.1 类加载过程

复制代码
加载 → 验证 → 准备 → 解析 → 初始化 → 使用 → 卸载
  │      │      │      │      │
  ▼      ▼      ▼      ▼      ▼
读取    校验   分配    符号    执行
字节码  格式   内存    引用    <clinit>
              (零值)  替换    方法

5.2 双亲委派模型

复制代码
              ┌─────────────────┐
              │ Bootstrap       │ ← 核心类(rt.jar)
              │ ClassLoader     │
              └────────┬────────┘
                       │ 委派
              ┌────────▼────────┐
              │ Extension       │ ← 扩展类(ext/)
              │ ClassLoader     │
              └────────┬────────┘
                       │ 委派
              ┌────────▼────────┐
              │ Application     │ ← 应用类(classpath)
              │ ClassLoader     │
              └────────┬────────┘
                       │ 委派
              ┌────────▼────────┐
              │ Custom          │ ← 自定义类
              │ ClassLoader     │
              └─────────────────┘

加载流程:向上委派 → 找不到 → 向下加载

5.3 打破双亲委派

java 复制代码
// 方式1:重写loadClass方法
public class MyClassLoader extends ClassLoader {
    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        // 自定义加载逻辑
        if (name.startsWith("com.myapp")) {
            return findClass(name);
        }
        return super.loadClass(name);
    }
}

// 方式2:线程上下文类加载器
// SPI机制、Tomcat类加载、OSGi等
ClassLoader loader = Thread.currentThread().getContextClassLoader();

6. JVM调优参数

6.1 内存参数

参数 说明 示例
-Xms 初始堆大小 -Xms512m
-Xmx 最大堆大小 -Xmx2g
-Xmn 新生代大小 -Xmn256m
-Xss 栈大小 -Xss512k
-XX:MetaspaceSize 元空间初始大小 -XX:MetaspaceSize=256m
-XX:MaxMetaspaceSize 元空间最大大小 -XX:MaxMetaspaceSize=512m
-XX:NewRatio 老年代:新生代比例 -XX:NewRatio=2 (2:1)
-XX:SurvivorRatio Eden:Survivor比例 -XX:SurvivorRatio=8 (8:1:1)

6.2 GC参数

参数 说明 示例
-XX:+UseG1GC 使用G1 JDK9+默认
-XX:+UseZGC 使用ZGC JDK15+
-XX:MaxGCPauseMillis 最大GC停顿时间 -XX:MaxGCPauseMillis=200
-XX:GCTimeRatio GC时间占比 -XX:GCTimeRatio=99 (1%时间给GC)
-XX:+PrintGCDetails 打印GC详情 调试用
-Xlog:gc* GC日志(JDK9+) -Xlog:gc*:file=gc.log

6.3 常用调优组合

bash 复制代码
# 开发环境
java -Xms256m -Xmx512m -XX:+PrintGCDetails -jar app.jar

# 生产环境(G1)
java -Xms4g -Xmx4g -XX:+UseG1GC \
     -XX:MaxGCPauseMillis=200 \
     -XX:G1HeapRegionSize=8m \
     -Xlog:gc*:file=gc.log:time,uptime,level,tags \
     -jar app.jar

# 低延迟场景(ZGC)
java -Xms8g -Xmx8g -XX:+UseZGC \
     -Xlog:gc*:file=gc.log \
     -jar app.jar

7. GC日志分析

7.1 GC日志格式

bash 复制代码
# JDK 8格式
[GC (Allocation Failure) [PSYoungGen: 65536K->10240K(76288K)] 
65536K->10280K(251392K), 0.0123456 secs]

# JDK 9+格式
[0.123s][info][gc] GC(0) Pause Young (Allocation Failure) 
64M->10M(256M) 12.345ms

7.2 关键指标

指标 说明 健康范围
GC频率 每分钟GC次数 Young<1次/秒, Full<1次/小时
GC耗时 单次GC时间 Young<50ms, Full<1s
吞吐量 非GC时间占比 >95%
晋升速率 对象进入老年代速度 越低越好

7.3 分析工具

工具 说明
jstat 命令行统计
jmap 堆转储
jvisualvm 可视化监控
GCViewer GC日志分析器
GCEasy 在线GC分析 (gceasy.io)
bash 复制代码
# jstat查看GC情况
jstat -gcutil <pid> 1000

# 输出示例
  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT
  0.00  50.23  45.67  32.15  95.42  92.31   125    1.234     2    0.456    1.690

8. 实战:排查内存问题

8.1 常见问题

问题 症状 排查方法
内存泄漏 OOM,堆持续增长 堆转储分析
频繁Full GC 应用卡顿 GC日志分析
线程死锁 应用挂起 jstack
CPU飙升 响应慢 top + jstack

8.2 排查步骤

bash 复制代码
# 1. 查看GC情况
jstat -gcutil <pid> 1000

# 2. 如果频繁Full GC,生成堆转储
jmap -dump:format=b,file=heap.hprof <pid>

# 3. 使用MAT或jvisualvm分析
# 查找占用内存最多的对象
# 查找对象的引用链

# 4. 查看线程状态
jstack <pid> > threads.txt

# 5. 查看CPU使用
top -Hp <pid>  # 找到CPU最高的线程
printf "%x\n" <tid>  # 转16进制
jstack <pid> | grep <tid_hex>  # 找到对应线程

8.3 内存泄漏示例

java 复制代码
// ❌ 错误:静态集合不断添加对象
public class MemoryLeak {
    private static final List<Object> list = new ArrayList<>();
    
    public void add(Object obj) {
        list.add(obj);  // 永远不会被GC
    }
}

// ❌ 错误:未关闭资源
public void readFile() {
    InputStream is = new FileInputStream("file.txt");
    // 忘记close,导致资源泄漏
}

// ✅ 正确:使用try-with-resources
public void readFile() {
    try (InputStream is = new FileInputStream("file.txt")) {
        // 自动关闭
    }
}

// ❌ 错误:ThreadLocal未清理
private static ThreadLocal<User> userHolder = new ThreadLocal<>();

public void process() {
    userHolder.set(getUser());
    // 线程复用时,旧值不会被清理
}

// ✅ 正确:用完清理
public void process() {
    try {
        userHolder.set(getUser());
        // ... 业务逻辑
    } finally {
        userHolder.remove();
    }
}

9. 总结

知识点 核心内容
内存模型 堆、栈、方法区、程序计数器
GC算法 标记-清除、标记-整理、复制、分代
收集器 G1(默认)、ZGC(低延迟)
类加载 双亲委派、自定义ClassLoader
调优 -Xms/-Xmx、GC参数、日志分析
排查 jstat、jmap、jstack、MAT

💬 你遇到过JVM内存问题吗?用什么工具排查的?分享一下你的经验!

📌 下一篇我们将学习 Maven项目管理,掌握Java项目的构建与依赖管理!


📚 参考资料

相关推荐
Flittly1 小时前
【AgentScope Java新手村系列】(4)结构化输出
java·spring boot·spring·ai
何以解忧,唯有..1 小时前
Python 中的继承机制:从基础到高级用法详解
java·开发语言·python
Yiyaoshujuku2 小时前
化合物数据集API接口(数据结构及样例)
java·网络·数据结构
plainGeekDev2 小时前
算法刷题笔记:一维DP没那么难,状态想清楚就赢了一半
java·算法·面试
IceBing2 小时前
还在一个个连接 Arthas?这个开源平台支持批量诊断 JVM
java
eggrall2 小时前
Linux线程:并发编程的双刃剑
jvm
SL_staff2 小时前
《如何用规则引擎替代if-else?JVS-Rules可视化编排比硬编码强在哪里?》
java·低代码·架构
Sam_Deep_Thinking2 小时前
java中的class到底是个什么东西?
java·开发语言·面试
swordbob2 小时前
Spring 3 级缓存解决循环依赖
java·spring