news 2026/4/2 6:44:54

JVM原理总结

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JVM原理总结

JVM原理解析:内存模型、GC机制、类加载、执行引擎与调优实战

Java 虚拟机(JVM)是 Java 语言跨平台、自动内存管理、高性能的核心支撑。本文将从JVM 整体架构、内存模型、类加载机制、执行引擎、垃圾回收(GC)、内存分配、调优工具与参数七个维度,全面拆解 JVM 的底层原理,覆盖从字节码执行到内存优化的全链路知识,帮你构建完整的 JVM 知识体系。

一、JVM 整体架构:程序运行的“骨架”

JVM 的核心目标是加载并执行 Java 字节码文件,其架构由三大核心模块和辅助模块组成,各模块协同工作完成程序的编译与运行。

JVM 整体架构 = 类加载子系统 + 运行时数据区 + 执行引擎 + 辅助工具(垃圾回收器、本地方法接口)
  • 类加载子系统:负责将.class文件加载到内存,并完成验证、准备、解析、初始化,最终生成可被 JVM 使用的类对象。
  • 运行时数据区:JVM 运行时的内存空间,分为线程私有和线程共享区域,是内存管理的核心。
  • 执行引擎:负责执行字节码指令,包括解释器、即时编译器(JIT)、垃圾回收器等核心组件。
  • 本地方法接口(JNI):连接 Java 代码与 C/C++ 等本地代码,实现跨语言调用。
  • 本地方法库:存放本地方法的具体实现,如操作系统底层 API 封装。

二、运行时数据区:JVM 的“内存布局”

运行时数据区是 JVM 管理内存的核心区域,根据《Java 虚拟机规范(Java SE 8)》,其分为线程私有区域线程共享区域,不同区域有明确的功能和生命周期。

2.1 线程私有区域(与线程同生共死)

线程私有区域的生命周期与所属线程一致,线程创建时分配内存,线程销毁时释放内存,不存在多线程共享冲突问题。

内存区域核心作用内部结构异常类型
程序计数器1. 记录当前线程执行的字节码指令地址(行号)
2. 支持线程切换后恢复执行(线程上下文切换的核心)
3. 执行 Native 方法时,计数器值为undefined
无复杂结构,仅存储指令地址唯一不会抛出 OOM 的区域
虚拟机栈存储方法调用的栈帧(Stack Frame),每个方法从调用到执行完毕对应一个栈帧的入栈和出栈栈帧包含:
1. 局部变量表:存储方法参数和局部变量
2. 操作数栈:执行字节码指令的临时数据栈
3. 动态链接:指向运行时常量池的方法引用
4. 方法出口:记录方法执行完毕后返回的地址
1.StackOverflowError:栈深度超过虚拟机允许的最大值(如无限递归)
2.OutOfMemoryError:栈容量动态扩展时无法申请到足够内存
本地方法栈为 Native 方法(如java.lang.Thread.start0())提供内存空间结构与虚拟机栈类似,具体实现依赖底层操作系统同虚拟机栈

2.2 线程共享区域(随 JVM 启动/关闭而创建/销毁)

线程共享区域被所有线程共享,是内存泄漏、OOM 异常的高发区,也是 GC 的核心操作区域。

内存区域核心作用版本差异异常类型
堆(Heap)存储对象实例数组,是 JVM 最大的内存区域所有版本一致,可通过-Xms(初始堆大小)、-Xmx(最大堆大小)参数调整OutOfMemoryError: Java heap space:堆空间不足,无法创建新对象
方法区(Method Area)存储类信息(类名、父类、接口、字段、方法)、运行时常量池静态变量即时编译器编译后的代码缓存1. Java 7 及以前:通过永久代(PermGen)实现,受 JVM 内存限制
2. Java 8 及以后:移除永久代,改用元空间(Metaspace),直接使用本地内存,默认仅受物理内存限制
1. Java 7:OutOfMemoryError: PermGen space
2. Java 8+:OutOfMemoryError: Metaspace
运行时常量池方法区的一部分,存储编译期生成的字面量(如字符串常量)和符号引用(如类名、方法名),运行时可动态添加常量(如String.intern()1. Java 7:从永久代移至堆
2. Java 8+:属于元空间
同方法区
堆的细分:为 GC 优化而生

为了提高垃圾回收效率,堆被进一步划分为新生代老年代,比例默认是1:2(可通过-XX:NewRatio调整)。

  • 新生代:存储新创建的对象,特点是对象存活率低、回收频繁,采用标记-复制算法
    • 细分区域:Eden区(占 80%)、From Survivor区(占 10%)、To Survivor区(占 10%),比例可通过-XX:SurvivorRatio调整。
  • 老年代:存储长期存活的对象,特点是对象存活率高、回收频率低,采用标记-清除算法标记-整理算法
  • 永久代/元空间:注意!永久代/元空间不属于堆,是方法区的实现,很多人容易混淆。

三、类加载机制:字节码的“加载与初始化”

类加载子系统负责将磁盘上的.class文件加载到 JVM 内存,并转换为java.lang.Class对象,整个过程分为5 个阶段,其中加载、验证、准备、初始化四个阶段的顺序是固定的,解析阶段可在初始化之后执行(支持动态绑定)。

3.1 类加载的 5 个核心阶段

阶段核心操作关键细节
加载(Loading)1. 通过类的全限定名获取.class文件的二进制字节流
2. 将字节流转换为方法区的运行时数据结构
3. 在堆中生成一个代表该类的Class对象,作为方法区数据的访问入口
加载的来源:本地文件、网络(如RMI)、动态生成(如动态代理)、数据库等
验证(Verification)确保.class文件的字节流符合 JVM 规范,防止恶意字节码攻击验证内容:文件格式验证(如魔数0xCAFEBABE)、元数据验证、字节码验证、符号引用验证
准备(Preparation)为类的静态变量分配内存,并设置默认初始值(如int默认为 0,boolean默认为false注意:不会执行赋值语句,如public static int a = 1;,准备阶段a的值是 0,初始化阶段才会赋值为 1
解析(Resolution)将常量池中的符号引用转换为直接引用(如将类名转换为内存地址)解析对象:类或接口、字段、方法、方法类型等
初始化(Initialization)执行类的静态代码块静态变量赋值语句,是类加载过程中唯一由程序员控制的阶段初始化触发条件:
1. 首次访问类的静态变量或静态方法
2. 创建类的实例(new关键字)
3. 反射调用类的方法(Class.forName()
4. 初始化子类时,父类会先初始化
5. JVM 启动时,执行主类(main方法所在类)

3.2 类加载器与双亲委派模型

类加载器是实现“加载”阶段的核心组件,JVM 提供了3 种内置类加载器,同时支持自定义类加载器。

3.2.1 内置类加载器
类加载器加载范围父加载器
启动类加载器(Bootstrap ClassLoader)加载JRE/lib目录下的核心类库(如rt.jar),由 C++ 实现,不属于 Java 类
扩展类加载器(Extension ClassLoader)加载JRE/lib/ext目录下的扩展类库,由 Java 实现启动类加载器
应用程序类加载器(Application ClassLoader)加载用户类路径(ClassPath)下的类,由 Java 实现扩展类加载器
3.2.2 双亲委派模型

双亲委派模型是类加载器的核心工作机制,其核心规则是:

  1. 当一个类加载器收到加载请求时,首先委托父加载器加载,而非自己直接加载。
  2. 父加载器无法加载该类时(在自己的加载范围内找不到),子加载器才会尝试自己加载。

优点

  • 避免类的重复加载:确保同一个类在 JVM 中只有一个Class对象。
  • 保证核心类库的安全:防止用户自定义的类(如java.lang.String)替换 JVM 核心类。

破坏场景

  • 为了实现热部署(如 Tomcat 的WebappClassLoader)。
  • 为了实现动态代理(如JDK 动态代理)。

四、执行引擎:字节码的“翻译与执行”

执行引擎是 JVM 的“心脏”,负责将运行时数据区中的字节码指令转换为机器指令执行,核心组件包括解释器即时编译器(JIT)垃圾回收器

4.1 解释器

  • 原理:采用逐行解释执行的方式,将字节码指令翻译为机器指令,执行一条,翻译一条。
  • 优点:启动速度快,适合短时间运行的程序(如脚本)。
  • 缺点:执行效率低,因为每次执行都需要重新翻译。
  • 核心组件Bytecode Interpreter,JVM 启动时默认使用解释器执行。

4.2 即时编译器(JIT)

为了解决解释器执行效率低的问题,JVM 引入了即时编译器,其核心目标是将热点代码编译为机器码,提高执行效率

4.2.1 热点代码的判定

JVM 通过热点计数器判定热点代码,分为两种计数器:

  • 方法调用计数器:统计方法被调用的次数,超过阈值(默认 10000)则标记为热点方法。
  • 回边计数器:统计循环体执行的次数,超过阈值则标记为热点循环。
4.2.2 JIT 编译的优化策略

JIT 编译器会对热点代码进行一系列优化,常见优化手段包括:

  1. 方法内联:将被调用的小方法的代码直接嵌入调用方,减少方法调用开销。
  2. 逃逸分析:分析对象的作用域,若对象未逃逸出方法,则可进行栈上分配(避免 GC)、标量替换(将对象拆分为基本类型)、同步消除(移除无用的锁)。
  3. 常量折叠:将编译期可知的常量表达式直接计算结果,如int a = 1 + 2;优化为int a = 3;
4.2.3 JIT 的两种编译器

HotSpot 虚拟机提供了两种 JIT 编译器,可通过参数调整:

  • C1 编译器:轻量级编译器,编译速度快,优化程度较低,适合客户端程序。
  • C2 编译器:重量级编译器,编译速度慢,优化程度高,适合服务端程序。
  • 分层编译(Java 7+ 默认开启):结合 C1 和 C2 的优点,先由 C1 编译,再由 C2 进一步优化。

4.3 本地方法接口(JNI)

当字节码执行到native方法时,执行引擎会通过 JNI 调用本地方法库中的 C/C++ 实现,流程如下:

  1. JVM 加载本地方法库(如System.loadLibrary())。
  2. 将 Java 类型转换为 C/C++ 类型(如jint对应int)。
  3. 调用本地方法的实现函数。
  4. 将 C/C++ 执行结果转换为 Java 类型并返回。

五、垃圾回收(GC)机制:内存的“自动清洁工”

GC 是 JVM 最核心的特性之一,其目标是自动识别并回收不再被引用的对象,释放内存空间,避免内存泄漏和 OOM 异常。GC 的核心流程是:判断对象存活 → 选择回收算法 → 执行回收操作

5.1 判断对象是否存活的核心算法

这是 GC 的前提,只有确定对象“无用”,才能进行回收。

算法原理优点缺点JVM 应用
引用计数法给每个对象添加一个引用计数器,被引用时 +1,引用失效时 -1;计数器为 0 的对象可回收实现简单,判定效率高无法解决循环引用问题(如 A 引用 B,B 引用 A,两者无外部引用,但计数器不为 0)未采用
可达性分析算法GC Roots为起点,向下遍历对象引用链;若对象不在任何引用链上,则判定为可回收对象解决了循环引用问题实现复杂,需要暂停用户线程(STW)HotSpot 虚拟机默认采用
GC Roots 的组成

GC Roots 是 JVM 中公认的“存活对象”,包括以下 4 类:

  1. 虚拟机栈中局部变量表引用的对象。
  2. 本地方法栈中 Native 方法引用的对象。
  3. 方法区中类静态属性引用的对象。
  4. 方法区中常量引用的对象。
引用类型的扩展

Java 提供了 4 种引用类型,不同类型的对象有不同的回收策略:

引用类型回收时机应用场景
强引用只有当强引用失效时,对象才会被回收普通对象引用(如Object obj = new Object()
软引用内存不足时,对象会被回收缓存(如SoftReference
弱引用每次 GC 时,对象都会被回收缓存(如WeakReferenceWeakHashMap
虚引用随时可能被回收,仅用于跟踪对象的回收状态管理直接内存(如PhantomReferenceCleaner

5.2 核心垃圾回收算法

不同算法适用于不同的内存区域,各有优劣,JVM 采用分代收集算法结合多种基础算法。

算法核心步骤优点缺点适用区域
标记-清除算法(Mark-Sweep)1. 标记:遍历所有对象,标记可回收对象
2. 清除:遍历堆,回收标记对象的内存
实现简单,无需移动对象1. 产生内存碎片(大量不连续的内存块,无法分配大对象)
2. 效率低(标记和清除均需遍历全堆)
老年代
标记-复制算法(Mark-Copy)1. 将内存分为两块大小相等的区域,只用其中一块
2. 标记存活对象,复制到另一块区域
3. 清空原区域的所有对象
1. 无内存碎片
2. 回收效率高
内存利用率低(仅 50%)新生代(优化:只划分一块 Eden 区和两块小的 Survivor 区,利用率提升至 90%)
标记-整理算法(Mark-Compact)1. 标记可回收对象
2. 将存活对象向内存一端移动,紧凑排列
3. 清除边界外的所有对象
1. 无内存碎片
2. 内存利用率高
效率低(需移动对象,涉及内存拷贝)老年代
分代收集算法结合新生代和老年代的特点:
1. 新生代:对象存活率低,用标记-复制算法
2. 老年代:对象存活率高,用标记-清除/标记-整理算法
兼顾效率和内存利用率实现复杂整个堆(JVM 默认算法)

5.3 常用垃圾收集器

垃圾收集器是回收算法的具体实现,不同收集器适用于不同的应用场景,HotSpot 虚拟机提供了多种收集器,可通过参数指定。

收集器适用区域核心特点垃圾回收停顿(STW)应用场景JVM 参数
Serial GC新生代 + 老年代单线程回收,采用标记-复制(新生代)+ 标记-整理(老年代)STW 时间长单核 CPU、小型应用(如桌面程序)-XX:+UseSerialGC
ParNew GC新生代Serial GC 的多线程版本,支持与 CMS 配合STW 时间比 Serial 短多核 CPU、追求低延迟的应用-XX:+UseParNewGC
Parallel Scavenge GC新生代多线程回收,目标是提高吞吐量(运行用户代码时间/总时间)STW 时间较短后台运算、批量处理任务(如数据统计)-XX:+UseParallelGC
Parallel Old GC老年代Parallel Scavenge 的老年代版本,采用标记-整理算法STW 时间较短与 Parallel Scavenge 配合,适用于高吞吐量场景-XX:+UseParallelOldGC
CMS GC(Concurrent Mark Sweep)老年代基于标记-清除算法,分 4 步执行:
1. 初始标记(STW,标记 GC Roots 直接引用的对象)
2. 并发标记(无 STW,遍历引用链)
3. 重新标记(STW,修正并发标记的偏差)
4. 并发清除(无 STW,回收对象)
STW 时间极短追求低延迟的 Web 应用(如电商、金融)-XX:+UseConcMarkSweepGC
G1 GC(Garbage-First)整个堆将堆划分为多个大小相等的 Region,兼顾吞吐量和低延迟,支持预测性 STW 时间STW 时间可预测大型应用、多核 CPU 环境(如服务器)-XX:+UseG1GC
ZGC整个堆利用着色指针和读屏障技术,几乎无停顿,支持 TB 级内存停顿时间 < 10ms超大型应用、低延迟要求极高的场景(如云计算)-XX:+UseZGC
Shenandoah GC整个堆与 ZGC 类似,低停顿,支持大内存停顿时间 < 10ms超大型应用-XX:+UseShenandoahGC

六、内存分配与回收策略:对象的“安家落户”

JVM 对对象的内存分配遵循**“优先新生代,晋升老年代”的原则,同时针对大对象、长期存活对象有特殊策略,核心目标是减少 GC 次数,提高程序性能**。

6.1 核心分配策略

  1. 对象优先在 Eden 区分配

    • 当创建新对象时,JVM 优先将对象分配到新生代的 Eden 区。
    • 当 Eden 区空间不足时,触发Minor GC(新生代 GC):将 Eden 和 From Survivor 中存活的对象复制到 To Survivor 区,然后清空 Eden 和 From Survivor 区;同时将对象的年龄计数器 +1
  2. 大对象直接进入老年代

    • 大对象指需要大量连续内存空间的对象(如长字符串、大数组)。
    • 为了避免大对象在新生代中频繁复制(浪费时间),JVM 提供参数-XX:PretenureSizeThreshold,超过该阈值的对象直接分配到老年代。
  3. 长期存活的对象进入老年代

    • 每个对象都有一个年龄计数器,每经历一次 Minor GC 且存活,年龄 +1。
    • 当对象年龄达到阈值(默认 15),会被晋升到老年代,阈值可通过-XX:MaxTenuringThreshold调整。
  4. 动态年龄判断

    • 新生代的 Survivor 区中,相同年龄的所有对象大小总和超过 Survivor 区的一半时,年龄大于等于该年龄的对象,可直接晋升老年代,无需等待阈值。
  5. 老年代空间分配担保

    • 在触发 Minor GC 前,JVM 会检查老年代最大可用连续空间是否大于新生代所有对象总大小。
    • 若大于,则 Minor GC 安全执行;若小于,则检查-XX:HandlePromotionFailure参数是否开启:
      • 开启:尝试执行 Minor GC,失败则触发Full GC(整堆回收)。
      • 关闭:直接触发 Full GC。

6.2 特殊分配策略:栈上分配

通过 JIT 的逃逸分析,若对象未逃逸出方法(即对象的作用域仅限于方法内部),JVM 会将对象分配在虚拟机栈的局部变量表中,而非堆中。

  • 优点:对象随方法执行完毕而销毁,无需 GC 参与,减少内存开销。
  • 开启参数-XX:+DoEscapeAnalysis(Java 7+ 默认开启)。

七、JVM 调优

JVM 调优的核心目标是减少 GC 次数、降低 STW 时间、提高程序吞吐量或降低延迟,调优的前提是明确业务场景(吞吐量优先或延迟优先)。

7.1 调优的核心步骤

  1. 监控运行状态:使用工具收集 JVM 运行数据,如堆内存使用情况、GC 次数、STW 时间。
  2. 分析瓶颈:根据监控数据定位问题,如频繁 Full GC、STW 时间过长、内存泄漏等。
  3. 调整参数:根据瓶颈调整 JVM 参数,如堆大小、收集器类型、新生代比例等。
  4. 验证效果:重新运行程序,监控调优后的指标,迭代优化。

7.2 常用调优工具

工具核心功能适用场景
jps列出正在运行的 JVM 进程查看进程 ID
jstat监控 JVM 内存和 GC 状态实时查看堆内存使用、GC 次数、STW 时间
jmap生成堆转储快照(heap dump分析内存泄漏、大对象分布
jhat分析堆转储快照查看对象数量、引用关系
jstack生成线程快照(thread dump排查死锁、线程阻塞
VisualVM可视化监控工具,整合 jps、jstat、jmap 等功能图形化分析 JVM 运行状态
GCViewer分析 GC 日志可视化 GC 趋势、STW 时间分布

7.3 核心调优参数

参数分类核心参数作用示例
堆内存参数-Xms设置初始堆大小,建议与-Xmx相同,避免堆动态扩展-Xms2g
-Xmx设置最大堆大小-Xmx4g
-XX:NewRatio设置新生代与老年代的比例,默认 2(新生代:老年代=1:2)-XX:NewRatio=1
-XX:SurvivorRatio设置 Eden 区与 Survivor 区的比例,默认 8(Eden:From:To=8:1:1)-XX:SurvivorRatio=6
收集器参数-XX:+UseSerialGC使用 Serial 收集器-
-XX:+UseParallelGC使用 Parallel Scavenge 收集器-
-XX:+UseConcMarkSweepGC使用 CMS 收集器-
-XX:+UseG1GC使用 G1 收集器-
GC 日志参数-XX:+PrintGCDetails打印详细 GC 日志-
-XX:+PrintGCTimeStamps打印 GC 发生的时间戳-
-Xloggc:/path/to/gc.log将 GC 日志输出到指定文件-
其他参数-XX:MaxTenuringThreshold设置对象晋升老年代的年龄阈值,默认 15-XX:MaxTenuringThreshold=10
-XX:PretenureSizeThreshold设置大对象直接进入老年代的阈值-XX:PretenureSizeThreshold=1048576(1MB)
-XX:+DoEscapeAnalysis开启逃逸分析-

八、核心总结

JVM 的运行是一个多模块协同工作的复杂过程,核心知识点可归纳为:

  1. 架构:类加载子系统加载字节码,运行时数据区管理内存,执行引擎执行指令。
  2. 内存:线程私有区域(程序计数器、虚拟机栈、本地方法栈)和线程共享区域(堆、方法区),堆是 GC 的核心。
  3. 类加载:5 个阶段 + 双亲委派模型,保证类的安全与唯一性。
  4. 执行引擎:解释器 + JIT 编译器,兼顾启动速度和执行效率;逃逸分析实现栈上分配,优化性能。
  5. GC:可达性分析判断对象存活,分代收集算法是核心策略,不同收集器适配不同业务场景。
  6. 调优:监控 → 分析 → 调整 → 验证,核心是根据业务场景选择合适的收集器和参数。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/1 19:50:49

毕业季必看!6款AI论文神器实测:真实参考文献、轻松搞定毕业论文

如果你是正在熬夜赶Deadline的毕业生... 如果你正盯着电脑屏幕发呆&#xff0c;被导师的“进度催命符”轰炸&#xff1b;如果你翻遍知网只为找几篇能用的参考文献&#xff0c;却被高昂的查重费压得喘不过气&#xff1b;如果你是囊中羞涩的大学生&#xff0c;或是怕延毕的研究生…

作者头像 李华
网站建设 2026/3/31 15:07:03

vue和springboot框架开发的旅游分享点评网系统_c6l4qb5f

文章目录 具体实现截图主要技术与实现手段关于我本系统开发思路java类核心代码部分展示结论源码lw获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01; 具体实现截图 同行可拿货,招校园代理 vuesprivuespringboot_c6l4qb5f 框架开发的旅游分享…

作者头像 李华
网站建设 2026/3/30 12:10:21

vue和springboot框架开发的民宿预定信息退订系统_777cb4oy

文章目录具体实现截图主要技术与实现手段关于我本系统开发思路java类核心代码部分展示结论源码lw获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;具体实现截图 同行可拿货,招校园代理 vuesprivuespringboot_777cb4oy 框架开发的民宿预定…

作者头像 李华
网站建设 2026/3/26 23:50:20

concurrentHashMap原理

concurrentHashMap的是为了解决HashMap在并发环境中出现的线程安全问题&#xff0c;同时也优化了HashTable在高并发中存在的性能问题&#xff0c;让其性能更接近于HashMap。高并发问题HashMap1.数据丢失问题2.JDK1.7采用头插法&#xff0c;会导致链表成环&#xff0c;抛出Concu…

作者头像 李华
网站建设 2026/4/1 2:27:44

FPC电路板先贴补强还是先SMT?正确顺序你选对了吗?

明明设计没问题&#xff0c;但SMT贴片后板子却弯曲起翘、元件浮起、甚至板子报废&#xff1f;这&#xff01;可能是补强贴合顺序埋下的雷&#xff01;FPC设计中&#xff0c;补强贴合顺序是最容易被忽视却又至关重要的环节。搞错顺序&#xff0c;轻则导致板子无法做SMT&#xff…

作者头像 李华