公司动态

【JVM原理详解】03-JVM整体架构全景图

📅 2026/7/19 22:48:52
【JVM原理详解】03-JVM整体架构全景图
JVM整体架构全景图引言前两篇文章分别从跨平台机制和发展历史两个视角帮你建立了对JVM的初步印象。现在到了最关键的一步——把JVM的内部结构完整地拆解开来。JVM是一个精密的系统它的架构设计体现了虚拟机领域的众多经典思想。本篇将俯瞰JVM的整体架构帮你建立起一个清晰的心理地图。当你理解了三大子系统的职责和协作方式后续深入类加载机制、运行时数据区和GC优化时就能始终知道当前讨论的内容在整个架构中的位置。JVM架构全景图下面是一幅JVM整体架构的ASCII图。建议你花一分钟仔细阅读把每个模块的位置印在脑海中。后续所有文章的内容都可以回溯到这张图上。┌──────────────────────────────────────────────────────────────────────┐ │ JVM 整体架构 │ ├──────────────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ 1. 类加载器子系统 (Class Loader Subsystem) │ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ │ │ Bootstrap │─▶│ Extension/ │─▶│ Application │ │ │ │ │ │ ClassLoader │ │ Platform │ │ ClassLoader │ │ │ │ │ │ (启动类加载器)│ │ ClassLoader │ │ (应用类加载器) │ │ │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ │ │ │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ │ │ ┌─────────┐ ┌─────────┐ ┌──────────┐ │ │ │ │ │ Loading │──▶│ Linking │──▶│Initialize│ 双亲委派 │ │ │ │ │ (加载) │ │ (链接) │ │ (初始化) │ 模型 │ │ │ │ └─────────┘ └─────────┘ └──────────┘ │ │ │ └──────────────────────────┬───────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ 2. 运行时数据区 (Runtime Data Areas) │ │ │ │ │ │ │ │ ┌──────────────────────────────────────────────┐ │ │ │ │ │ 线程共享区域 (Thread-Shared) │ │ │ │ │ │ ┌─────────────────┐ ┌───────────────────┐ │ │ │ │ │ │ │ Heap │ │ Method Area │ │ │ │ │ │ │ │ (堆/GC堆) │ │ (方法区/元空间) │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ┌─────────────┐ │ │ ┌───────────────┐ │ │ │ │ │ │ │ │ │ Young Gen │ │ │ │ 类元数据 │ │ │ │ │ │ │ │ │ │ (EdenS0S1)│ │ │ │ 运行时常量池 │ │ │ │ │ │ │ │ │ ├─────────────┤ │ │ │ 静态变量 │ │ │ │ │ │ │ │ │ │ Old Gen │ │ │ │ JIT编译缓存 │ │ │ │ │ │ │ │ │ │ (Tenured) │ │ │ │ 方法字节码 │ │ │ │ │ │ │ │ │ └─────────────┘ │ │ └───────────────┘ │ │ │ │ │ │ │ └─────────────────┘ └───────────────────┘ │ │ │ │ │ └──────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ ┌──────────────────────────────────────────────┐ │ │ │ │ │ 线程私有区域 (Thread-Private) │ │ │ │ │ │ │ │ │ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────┐ │ │ │ │ │ │ │ JVM Stack │ │Native Method │ │ PC │ │ │ │ │ │ │ │ (虚拟机栈) │ │ Stack │ │ 寄存器│ │ │ │ │ │ │ │ │ │ (本地方法栈) │ │ │ │ │ │ │ │ │ │ ┌──────────┐ │ │ │ │ │ │ │ │ │ │ │ │ │栈帧 Frame│ │ │ (Native方法 │ │ │ │ │ │ │ │ │ │ │-局部变量表│ │ │ 调用栈) │ │ │ │ │ │ │ │ │ │ │-操作数栈 │ │ │ │ │ │ │ │ │ │ │ │ │ │-动态链接 │ │ │ │ │ │ │ │ │ │ │ │ │ │-方法返回 │ │ │ │ │ │ │ │ │ │ │ │ │ └──────────┘ │ │ │ │ │ │ │ │ │ │ │ └──────────────┘ └──────────────┘ └──────┘ │ │ │ │ │ └──────────────────────────────────────────────┘ │ │ │ └──────────────────────────┬───────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ 3. 执行引擎 (Execution Engine) │ │ │ │ │ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ │ │ 解释器 │ │ JIT编译器 │ │ 垃圾回收器 │ │ │ │ │ │ (Interpreter)│ │ (C1 / C2) │ │ (Garbage │ │ │ │ │ │ │ │ │ │ Collector) │ │ │ │ │ │ 逐条解释字节码 │ │ 热点代码编译 │ │ 自动内存管理 │ │ │ │ │ │ 立即开始执行 │ │ 为本地机器码 │ │ 回收无用对象 │ │ │ │ │ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ │ │ │ │ │ │ │ │ │ │ ▼ ▼ │ │ │ │ │ ┌─────────────────────────────────┐ │ │ │ │ │ │ 分层编译 (Tiered Compilation)│ │ │ │ │ │ │ Level 0: 解释执行 │ │ │ │ │ │ │ Level 1-3: C1编译 (渐进优化) │◀───────┘ │ │ │ │ │ Level 4: C2编译 (深度优化) │ │ │ │ │ └─────────────────────────────────┘ │ │ │ └──────────────────────────────────────────────────────────┘ │ │ │ └──────────────────────────────────────────────────────────────────────┘三大子系统详解子系统一类加载器子系统类加载器子系统是整个JVM的入口。没有它字节码就只是磁盘上的二进制文件无法被JVM执行。这套子系统负责三项核心任务加载Loading通过类的全限定名找到对应的.class文件或网络数据流读取其二进制数据在内存中生成代表该类的java.lang.Class对象。加载源可以是本地文件系统、jar包、网络Applet时代的URLClassLoader甚至运行时动态生成动态代理、CGLIB等。链接Linking分为三个子阶段——验证确保字节码符合JVM规范不会破坏JVM安全→准备为类的静态变量分配内存并设置默认值注意此时尚未执行Java代码→解析将常量池中的符号引用替换为直接引用。初始化Initialization执行类构造器clinit方法为静态变量赋初始值和执行静态代码块。这是类加载过程中首次执行Java代码的阶段。类加载器的组织遵循双亲委派模型Parent Delegation ModelBootstrap ClassLoader (启动类加载器加载rt.jar/jmods中的核心类) ▲ │ 父加载器 Platform/Extension ClassLoader (平台/扩展类加载器) ▲ │ 父加载器 Application ClassLoader (应用类加载器加载classpath下的类) ▲ │ 父加载器 自定义 ClassLoader双亲委派的工作方式是一个类加载器收到加载请求时首先委托给父加载器尝试加载只有父加载器加载失败时才自己尝试。这种机制确保了Java核心类库如java.lang.String始终由Bootstrap ClassLoader加载避免了用户自定义的同名类替换核心类带来的安全风险。子系统二运行时数据区运行时数据区是JVM的内存大脑。JVM在执行Java程序时会将它管理的内存划分为若干个不同的数据区域每个区域有各自的用途和生命周期。这些区域可以分为两大类线程共享区域所有线程都能访问堆HeapJVM内存中最大的一块几乎所有对象实例都在这里分配。堆也是GC管理的主要区域。在分代GC的视角下堆被划分为新生代Young Generation又分为Eden区和两个Survivor区和老年代Old Generation。JDK 8之前还有永久代的概念用于存储类元数据但从JDK 8起已被元空间取代。方法区Method Area存储已被JVM加载的类信息、常量、静态变量、JIT编译后的代码缓存等。在HotSpot的实现中JDK 8之前方法区的实现是永久代PermGenJDK 8之后改为使用本地内存的元空间Metaspace。这一变化解决了永久代-XX:MaxPermSize参数调优困难、容易OOM的痛点——元空间默认只受限于系统可用内存。线程私有区域每个线程独有随线程创建而创建随线程结束而销毁程序计数器PC Register当前线程所执行的字节码指令的行号指示器。分支、循环、跳转、异常处理、线程恢复等基础功能都依赖PC寄存器。如果线程执行的是Native方法PC寄存器的值为undefined。Java虚拟机栈JVM Stack描述Java方法执行的内存模型。每个方法执行时JVM都会创建一个栈帧Stack Frame用于存储局部变量表、操作数栈、动态链接、方法返回地址等信息。方法的调用到执行完成对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。当线程请求的栈深度超过允许的最大值时抛出StackOverflowError当栈动态扩展无法获得足够内存时抛出OutOfMemoryError。本地方法栈Native Method Stack与虚拟机栈功能相似但服务于Native方法通过JNI调用的C/C代码。在HotSpot中本地方法栈和虚拟机栈是合并实现的不单独区分。子系统三执行引擎执行引擎是JVM的心脏负责将字节码指令翻译为机器指令并执行。HotSpot的执行引擎采用混合模式解释器Interpreter逐条读取字节码指令查表找到对应的机器码片段执行。启动速度快但相同代码每次执行都要重复解释。HotSpot使用模板解释器——每条字节码指令在JVM启动时预先生成一段机器码stub执行时直接跳转即可。JIT编译器Just-In-Time Compiler将热点方法/循环的字节码一次性编译为本地机器码后续直接执行编译后的代码不再需要解释。HotSpot内置了两个JIT编译器C1编译器Client Compiler编译速度快但优化程度较低。适合对启动速度敏感的场景如桌面应用。C2编译器Server Compiler编译速度慢但能进行深度优化内联、逃逸分析、循环展开、无用代码消除等。适合长时间运行的服务器端应用。在JDK 7及之后HotSpot默认启用分层编译Tiered Compilation结合C1和C2的优势Level 0: 解释执行不收集性能数据→ Level 1: C1简单编译不收集profiling数据→ Level 2: C1编译进行简单profiling→ Level 3: C1编译进行完整profiling→ Level 4: C2深度优化编译系统根据方法的热度决定其当前应处于哪个编译层级。热点方法会逐步从Level 0提升到Level 4非热点方法可能停留在较低层级甚至退回到解释执行。垃圾回收器Garbage Collector自动管理堆内存回收不再使用的对象。GC是JVM与C/C等需要手动内存管理的语言最显著的差异之一。HotSpot提供多种GC策略——Serial、Parallel、CMSJDK 14移除、G1JDK 9起默认、ZGCJDK 15起生产可用、Shenandoah等。GC的运行会触发Stop-The-World暂停所有用户线程暂停等待GC完成不同的GC算法在吞吐量和暂停时间之间做出不同的权衡。数据在JVM中的流转过程以一段简单的Java代码为例追踪数据在JVM中的完整流转路径publicclassDataFlow{publicstaticvoidmain(String[]args){CalculatorcalcnewCalculator();// ①创建对象intresultcalc.add(10,20);// ②调用方法System.out.println(result);// ③输出结果}}classCalculator{publicintadd(inta,intb){returnab;}}当执行java DataFlow时类加载阶段AppClassLoader找到DataFlow.class和Calculator.class加载到方法区。经过验证、准备为静态变量分配默认值、解析符号引用→直接引用、初始化执行clinit方法调用阶段主线程的PC寄存器指向main方法的字节码JVM栈中压入main的栈帧。字节码new #2触发在堆中分配Calculator对象实例局部变量表第0号槽位存储该对象的引用对象方法调度calc.add(10, 20)被编译为invokevirtual指令。JVM先在堆中定位calc对象通过其对象头中的类型指针找到方法区中的Calculator类元数据再定位到add方法的字节码。add方法执行时JVM栈压入新栈帧局部变量表中存储this引用和两个int参数10, 20操作数栈计算iadd的结果30方法返回add的栈帧弹出返回值30放入main栈帧的操作数栈顶。main方法继续执行后续字节码输出结果垃圾回收当Calculator对象不再被任何引用指向时GC线程会在合适的时机回收其在堆中的内存实践要点内存区域与JVM参数运行时数据区的大小通过JVM启动参数控制。堆大小用-Xms初始和-Xmx最大设定栈大小用-Xss设定默认约1MB/线程元空间用-XX:MetaspaceSize和-XX:MaxMetaspaceSize控制。生产环境应始终显式设置-Xmx避免JVM使用默认值通常是物理内存的1/4导致意外OOM或被操作系统OOM Killer杀死。栈帧与递归深度每个栈帧的大小在编译期就已确定。递归调用过深时即使堆内存充足也会因栈空间耗尽而抛出StackOverflowError。将递归改写为迭代、适当增大-Xss或使用尾递归优化JVM不直接支持尾递归优化需要编译器协助是常用的应对措施。分层编译的观测通过-XX:PrintCompilation可以看到方法在不同编译层级间的迁移过程。通过-XX:-TieredCompilation可以关闭分层编译不推荐生产环境使用除非在排查特定的JIT编译问题。JDK 8中可通过-XX:UnlockDiagnosticVMOptions -XX:PrintInlining查看方法内联的详细信息这是理解JIT优化的有力工具。元空间溢出与永久代溢出JDK 8后的元空间溢出报java.lang.OutOfMemoryError: MetaspaceJDK 8前报java.lang.OutOfMemoryError: PermGen space。排查时需要关注是否有大量的动态代理类生成CGLIB、Javaassist等框架或类加载器泄漏导致已加载的类无法被卸载。元空间的优势在于它不会被固定大小的永久代所限制——但它仍然受限于系统可用内存监控和告警依然是必要的。架构图是理解后续知识的锚点当后续文章深入讲解类加载器机制、堆内存管理或GC算法时返回来看这张全景图你就能迅速定位到当前讨论的内容处于JVM的哪个子系统、哪个内存区域。建议收藏本篇文章作为后续学习的索引。小结JVM由三大子系统构成类加载器子系统负责加载和初始化类→运行时数据区存储各类数据的五块内存区域→执行引擎解释执行JIT编译GC运行时数据区按线程共享/私有划分为两类理解它们的生命周期和存储内容对性能调优和问题排查至关重要执行引擎的解释器JIT编译器混合模式兼顾了启动速度与峰值性能分层编译进一步细化了优化策略方法区从永久代到元空间的演变是JVM内存管理的重要里程碑减少了OOM风险这张全景图是后续深入各个子系统的基础框架每当我们讨论具体的知识点时都可以回到这张图上找到它的位置更多资料【JVM调优实战】