JVM - Java体系结构
# 1. 引言:Java工程师的“痛”
作为一名Java工程师,您是否也曾经历过线上系统突然卡顿、服务无响应,甚至直接抛出OOM(Out of Memory)错误的窘境?
许多开发者可能会遇到以下挑战:
- 线上问题难定位: 面对生产环境中的JVM GC(垃圾收集)问题,感到束手无策,不知从何处着手分析和解决。
- 参数配置茫然: 新项目上线时,对于五花八门的JVM参数感到困惑,往往选择默认配置,结果可能导致系统在高负载下崩溃。
- 面试“老大难”: 每次面试前都要死记硬背JVM的理论概念,但面试官更倾向于考察实际项目中的调优经验,例如如何设置VM参数、如何排查GC或OOM问题,导致回答时捉襟见肘。
现实情况是,大多数Java开发人员虽然熟练使用各种上层框架和技术(如Spring、MyBatis、微服务等),但对Java技术的核心——Java虚拟机(JVM)——却知之甚少。
部分有经验的开发者甚至认为,专注于应用层技术才是关键,底层基础并不重要。这其实是一种本末倒置的观念。如果把Java核心类库API比作可以直接套用的数学公式,那么JVM知识就好比是理解这些公式如何推导出来的过程。不了解底层原理,虽然也能使用高级语言编程,但对程序的运行机制、性能瓶颈、问题排查都会缺乏深度理解。
# 2. 架构师的日常思考与JVM的重要性
架构师的核心职责之一就是确保系统的性能和稳定性。他们每天思考的问题往往包括:
- 如何让系统运行得更快?
- 如何提前识别并规避潜在的系统瓶颈?
在许多高薪职位的招聘要求中,我们常常能看到对JVM能力的明确要求:
- 参与现有系统的性能优化、重构,保证平台性能和稳定性。
- 根据业务场景和需求,进行技术选型和技术方向决策。
- 能够独立架构和设计海量数据、高并发下的分布式解决方案。
- 识别和解决各类潜在系统风险,负责核心功能的架构设计与代码实现。
- 分析系统瓶颈,解决各种疑难杂症,进行性能调优。
# 为什么要深入学习JVM?
- 面试刚需: 国内外一线互联网公司(如BATJ、TMD、字节跳动等)在面试中非常看重候选人对JVM的理解深度。
- 中高级必备: 对于走向中高级、架构师岗位的程序员,JVM知识是进行项目管理、性能调优、故障排查的基础。
- 技术追求: 深入理解JVM的底层原理,如垃圾回收算法、JIT(Just-In-Time)编译器等,是技术极客精神的体现,有助于写出更高效、更健壮的代码。
# 3. Java 与 C++:内存管理的差异
Java与C++的一个显著区别在于内存管理方式。
- C/C++: 程序员需要手动进行内存的分配(如
malloc
,new
)和释放(如free
,delete
)。这给予了开发者极高的控制权,但也带来了内存泄漏(忘记释放)和野指针(释放后继续使用)等风险,管理复杂且容易出错。 - Java: JVM承担了内存分配和回收的职责。开发者通过
new
关键字创建对象时,JVM负责在堆内存中分配空间。当对象不再被引用时,JVM的垃圾收集(Garbage Collection, GC)机制会自动识别并回收这些对象所占用的内存。这极大地简化了内存管理,提高了开发效率,降低了内存相关错误的概率。
然而,自动垃圾收集并非万能药。不理解JVM的内存结构(如堆、栈、方法区)、对象生命周期以及GC的工作机制,仍然可能写出导致内存泄漏或性能问题的代码。因此,掌握JVM内部原理,是设计高扩展性应用、诊断运行时问题的基础,也是Java工程师能力进阶的必经之路。
# 4. JVM学习推荐书籍
系统性学习JVM,以下书籍是很好的选择:
- 《Java虚拟机规范(Java SE 8版)》:官方文档的中文翻译版。内容权威但可能过于枯燥,直接阅读官方英文版或作为参考手册可能更佳。
- 《深入理解Java虚拟机:JVM高级特性与最佳实践》(周志明著):国内该领域的经典之作,广受好评。建议阅读最新版(如第3版),内容覆盖全面且深入浅出。此书是学习JVM的首选读物之一。
此外,还有一些针对特定领域或有独到见解的书籍:
这些书籍可以作为深入特定主题(如性能调优、GC算法细节等)的补充阅读。
# 5. 蓬勃发展的Java生态圈
Java早已超越了一门编程语言的范畴,发展成为一个庞大且充满活力的技术平台、一种开源文化和一个全球性的开发者社区。
- 平台: Java虚拟机(JVM)是Java平台的核心。它不仅运行Java代码,还支持一系列基于JVM的语言,如Groovy、Scala、JRuby、Kotlin等。这些语言可以利用JVM的跨平台性、垃圾回收、即时编译等优势,并能与Java代码无缝互操作。
- 文化: Java社区推崇开源精神。无数优秀的第三方开源框架和库(如Tomcat, Struts, MyBatis, Spring, Netty, Hadoop, Spark等)极大地丰富了Java生态,提高了开发效率。甚至Java开发工具包(JDK)和JVM自身也有开源实现(如OpenJDK, Eclipse OpenJ9)。
- 社区: Java拥有全球最庞大的开发者社区之一,提供了海量的学习资源、技术论坛和开源项目支持。从桌面应用、嵌入式系统到大型企业级应用、后台服务、中间件,Java的应用无处不在,其应用场景之复杂、参与者之众多令人瞩目。
“一次编译,到处运行”(Write Once, Run Anywhere)是Java著名的口号。这得益于JVM的存在。开发者编写的Java源代码(.java
文件)被Java编译器编译成平台无关的字节码(.class
文件)。然后,这个字节码文件可以在任何安装了兼容版本JVM的操作系统和硬件平台上运行,由JVM负责将字节码解释或编译成本地机器指令执行。
随着Java 7引入的JSR-292(动态调用支持)等规范,JVM进一步增强了对非Java语言的支持。JVM本身并不关心运行在其上的代码是用哪种语言编写的,它只关心输入的字节码文件是否符合规范。只要一种编程语言的编译器能生成符合JVM规范的字节码文件(包含JVM指令集、符号表和其他辅助信息),那么这种语言编写的程序就能在JVM上运行。这体现了JVM的语言无关性。
# 6. 理解字节码(Bytecode)
通常我们说的“Java字节码”,是指由Java语言编译器(javac
)生成的字节码。但更准确地说,应该称为JVM字节码。因为任何能够编译成符合JVM规范的字节码文件的语言,其产物都是JVM字节码。
- 不同的编译器(例如
javac
、Scala编译器scalac
、Kotlin编译器kotlinc
)可以生成相同格式的JVM字节码文件(.class
文件)。 - 同一个字节码文件可以在不同实现的JVM(如HotSpot VM, OpenJ9 VM)以及不同操作系统上的JVM上运行。
Java虚拟机与Java语言之间没有必然的绑定关系。JVM的核心是与特定二进制文件格式——Class文件格式——相关联。Class文件包含了:
- JVM指令集(字节码): 虚拟机执行的基本操作。
- 符号表: 类、字段、方法等的名称和描述符信息。
- 其他辅助信息: 如常量池、异常表、代码行号信息、本地变量表等。
# 7. 多语言混合编程的趋势
在现代软件开发中,利用不同语言的优势来解决特定领域的问题变得越来越普遍。Java平台凭借其强大的JVM基础,成为了多语言混合编程的理想选择。
想象一个项目:
- 并行计算密集型任务使用Clojure(擅长并发)。
- Web前端展示层使用JRuby on Rails(开发效率高)。
- 核心业务逻辑和中间层使用Java(生态成熟、性能稳定)。
在这个项目中,每一层都可以使用最适合的语言开发。由于它们最终都运行在同一个JVM之上,各语言之间的交互可以非常顺畅,如同调用原生API一样自然,接口对开发者透明。
JVM平台对Java之外语言的支持正在不断增强。以JSR-292为代表的一系列改进(如Da Vinci Machine项目、Nashorn JavaScript引擎、invokedynamic
指令、java.lang.invoke
包等)正推动着Java虚拟机从一个“Java语言的虚拟机”演变成一个真正的“多语言虚拟机”。
# 8. Java发展史上的重大事件
了解Java和JVM的发展历程有助于我们理解当前技术的来龙去脉:
- 1990年: Sun公司的“Green Team”(James Gosling等人)开始开发一种新的语言,最初命名为Oak,后改为Java。
- 1995年: Sun正式发布Java和HotJava浏览器,Java首次公开亮相。
- 1996年: JDK 1.0发布。
- 1998年: JDK 1.2发布,引入JSP/Servlet、EJB规范,并将Java划分为J2SE(标准版)、J2EE(企业版)、J2ME(微型版),标志着Java向企业、桌面、移动三大领域进军。
- 2000年: JDK 1.3发布,Java HotSpot Virtual Machine正式发布并成为默认虚拟机。
- 2002年: JDK 1.4发布,古老的Classic VM退出历史舞台。
- 2003年: Scala和Groovy语言加入Java平台阵营。
- 2004年: JDK 1.5(更名为Java SE 5.0)发布,引入泛型、注解、自动装箱/拆箱等重要特性。
- 2006年: JDK 6发布。同年,Java宣布开源,建立了OpenJDK项目,HotSpot VM成为OpenJDK的默认虚拟机。
- 2007年: Clojure语言加入Java平台。
- 2008年: Oracle收购BEA Systems,获得JRockit虚拟机。
- 2009年: Twitter宣布将后台大部分服务从Ruby迁移到Scala,展示了JVM平台在大规模应用中的潜力。
- 2010年: Oracle收购Sun Microsystems,获得Java商标和核心资产HotSpot VM。Oracle至此拥有了两款市场领先的虚拟机(HotSpot和JRockit),并计划整合两者优势(HotRockit计划,后融入HotSpot)。
- 2011年: JDK 7发布。在JDK 1.7u4中,正式引入了新的垃圾回收器G1(Garbage-First)。
- 2014年: JDK 8发布,引入Lambda表达式、Stream API等重大特性,成为长期支持(LTS)版本,至今仍被广泛使用。
- 2017年: JDK 9发布,引入模块化系统(Jigsaw),并将G1设为默认垃圾回收器,取代了CMS(Concurrent Mark Sweep)。同年,IBM将其J9虚拟机开源,成立Eclipse OpenJ9社区。
- 2018年: Android Java侵权案判决,Google需向Oracle支付巨额赔偿。Oracle将Java EE(企业版)规范捐赠给Eclipse基金会,后者将其更名为Jakarta EE。同年,JDK 11发布,这是继JDK 8之后的又一个LTS版本,引入了ZGC(Z Garbage Collector)等新特性,并调整了JDK的授权许可模式(商业使用收费)。
- 2019年至今: Java进入快速发布周期(每6个月一个新版本),JDK 12引入Shenandoah GC,后续版本持续带来新特性和性能改进。新的LTS版本如JDK 17、JDK 21相继发布。
OpenJDK vs Oracle JDK: 在JDK 11之前,Oracle JDK包含一些OpenJDK中没有的商业特性。但从JDK 11开始,Oracle JDK和OpenJDK在功能上基本完全一致。主要区别在于授权许可和长期支持策略。OpenJDK是完全开源免费的,而Oracle JDK从JDK 11开始对商业用途收费(个人开发和测试免费)。
# 9. 虚拟机与Java虚拟机
# 什么是虚拟机?
虚拟机(Virtual Machine, VM)本质上是一台虚拟的计算机,它通过软件来模拟真实计算机的功能,执行特定的指令集。虚拟机大致可以分为两类:
- 系统虚拟机(System Virtual Machine): 如VirtualBox、VMware、Hyper-V等。它们完整地模拟物理计算机硬件,可以在其上安装和运行完整的操作系统(如Windows、Linux)。
- 程序虚拟机(Process Virtual Machine): 如Java虚拟机(JVM)、.NET CLR(Common Language Runtime)等。它们专门为执行单个程序而设计,提供一个独立于底层硬件和操作系统的运行环境。在JVM中执行的指令就是Java字节码。
无论是哪种虚拟机,运行在虚拟机上的软件都受限于虚拟机所提供的资源。
# 什么是Java虚拟机?
Java虚拟机(Java Virtual Machine, JVM)是一台执行Java字节码的虚拟计算机。它拥有自己完善的硬件架构(如处理器、堆栈、寄存器等)和相应的指令系统。
- 核心功能: JVM是Java字节码的运行环境。它负责加载
.class
文件,校验字节码,然后通过解释器逐条解释执行字节码,或者通过**即时编译器(JIT)**将热点代码(经常执行的代码)编译成本地机器码以提高执行效率。 - 语言无关性: JVM运行的是字节码,而非Java源代码。任何能编译成合规字节码的语言都可以在JVM上运行。
- 跨平台性: Java程序编译成字节码后,可以由不同平台上的JVM来执行,实现了“一次编译,到处运行”。
- 自动内存管理: JVM通过垃圾收集器自动管理内存分配和回收,减轻了开发者的负担。
Java技术的核心在于JVM,所有Java程序(以及其他JVM语言程序)最终都在JVM内部运行。JVM规范详细定义了每一条Java字节码指令的操作,包括如何获取操作数、如何处理操作数以及结果存放在哪里。
JVM的主要特点:
- 一次编译,到处运行 (Write Once, Run Anywhere)
- 自动内存管理 (Automatic Memory Management)
- 自动垃圾回收 (Automatic Garbage Collection)
# 10. JVM在系统中的位置
JVM是运行在操作系统之上的一个进程。它不直接与硬件交互,而是通过操作系统来访问底层硬件资源。
从Java技术体系结构来看,JVM处于核心位置,承载着Java程序(字节码)的运行:
# 11. JVM整体结构概览(以HotSpot为例)
HotSpot VM是目前市面上应用最广泛、性能最优的高性能虚拟机之一。它的主要架构特点包括:
解释器与即时编译器(JIT)并存:
- 程序启动时,通常由解释器首先执行字节码,响应速度快。
- JVM会监控代码执行情况(热点探测),识别出频繁执行的“热点代码”。
- JIT编译器会将这些热点代码编译成本地机器码,并缓存起来。后续执行这些代码时,直接运行编译后的机器码,性能大幅提升。
- 这种混合模式在程序响应时间和长期执行性能之间取得了很好的平衡。
内存区域划分: JVM规范定义了若干运行时数据区,HotSpot VM有其具体实现,主要包括:
- 方法区(Method Area): 存储已被加载的类信息、常量、静态变量、JIT编译后的代码等数据。(JDK 8及以后,元空间Metaspace取代了永久代PermGen作为方法区的实现)
- 堆(Heap): JVM管理的内存中最大的一块,几乎所有的对象实例和数组都在这里分配。是垃圾收集器管理的主要区域。
- 虚拟机栈(VM Stack): 每个线程私有,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法调用对应一个栈帧入栈和出栈。
- 本地方法栈(Native Method Stack): 为虚拟机使用到的Native方法服务。
- 程序计数器(Program Counter Register): 当前线程所执行的字节码的行号指示器。
执行引擎(Execution Engine): 负责执行字节码。包含解释器、JIT编译器、垃圾回收器(GC)。
本地方法接口(Native Interface): 用于调用本地(非Java)代码(如C/C++编写的库)。
本地方法库(Native Libraries): JVM自身或应用程序调用的本地代码库。
如今,得益于HotSpot VM等高性能虚拟机的不断优化,Java程序的运行性能已经取得了长足的进步,在很多场景下足以与C/C++程序相媲美。
# 12. Java代码执行流程
一个典型的Java程序从源代码到最终执行,大致经历以下步骤:
- 编写源代码: 开发者使用Java(或其他JVM语言)编写源代码文件(例如
.java
文件)。 - 编译: 使用相应语言的编译器(如
javac
)将源代码编译成字节码文件(.class
文件)。这个文件包含了平台无关的JVM指令。 - 加载: 程序运行时,JVM的类加载器(Class Loader)负责将需要的
.class
文件加载到内存中的方法区。 - 校验: 字节码校验器确保加载的字节码符合JVM规范,没有安全问题。
- 执行: 执行引擎开始执行字节码。
- 解释执行: 解释器逐行将字节码翻译成本地机器指令并执行。
- 编译执行(JIT): 对于热点代码,JIT编译器将其编译成本地机器码,提高后续执行速度。
- 垃圾回收: GC在后台运行,自动回收不再使用的对象所占用的内存。
- 与操作系统交互: JVM通过本地方法接口调用操作系统提供的功能(如文件I/O、网络通信、线程管理等)。
理论上,只要能设计一种语言并提供相应的编译器,能够生成符合JVM规范的字节码文件,那么这种新语言编写的程序就可以在JVM上运行。
# 13. JVM架构模型:基于栈 vs 基于寄存器
JVM指令集架构的设计选择了**基于栈(Stack-based)的方式,与之相对的是基于寄存器(Register-based)**的架构。这两种架构各有优劣:
基于栈的指令集架构(JVM采用):
- 特点:
- 指令通常不显式指定操作数来源,而是依赖于一个操作数栈(Operand Stack)。指令执行时,从栈顶弹出操作数,运算结果再压回栈顶。
- 指令集更小,大部分是零地址指令(不需要指定操作数地址)。
- 设计和实现相对简单,编译器实现也更容易。
- 不依赖硬件寄存器,因此可移植性极好,更容易实现跨平台。
- 适用于资源受限的系统。
- 缺点:
- 完成同一功能通常需要更多条指令。
- 执行过程中需要频繁地压栈和出栈,访存次数可能更多,理论上执行速度可能慢于寄存器架构。
基于寄存器的指令集架构(如x86汇编、Android Dalvik/ART VM):
- 特点:
- 指令通常会显式指定操作数所在的寄存器。
- 指令通常是一地址、二地址或三地址指令。
- 完成同一功能所需的指令数通常更少。
- 执行速度更快,因为寄存器访问速度远快于内存访问。
- 缺点:
- 指令集更大,设计更复杂。
- 严重依赖硬件寄存器,可移植性差。不同CPU架构的寄存器数量和结构不同,需要针对性编译。
- 编译器需要解决复杂的寄存器分配问题。
举例:计算 2 + 3
基于栈的指令(JVM字节码):
iconst_2 // 将整数常量2压入操作数栈 istore_1 // 将栈顶的2存入局部变量表索引为1的位置 (假设是变量i) iconst_3 // 将整数常量3压入操作数栈 istore_2 // 将栈顶的3存入局部变量表索引为2的位置 (假设是变量j) iload_1 // 将局部变量表中索引1的值(2)压入操作数栈 iload_2 // 将局部变量表中索引2的值(3)压入操作数栈 iadd // 从操作数栈弹出顶部的两个整数(3和2),相加,结果(5)压回栈顶 istore_3 // 将栈顶的结果(5)存入局部变量表索引为3的位置 (假设是变量a)
1
2
3
4
5
6
7
8基于寄存器的指令(类似x86汇编):
mov eax, 2 ; 将立即数2移动到寄存器eax add eax, 3 ; 将寄存器eax的值加上立即数3,结果仍在eax中
1
2
字节码反编译示例:
下面是一个简单的Java代码及其反编译后的JVM字节码,展示了基于栈的操作:
// 源文件: StackStruTest.java
public class StackStruTest {
public static void main(String[] args) {
int i = 2; // 对应字节码 0: iconst_2 和 1: istore_1
int j = 3; // 对应字节码 2: iconst_3 和 3: istore_2
int a = i + j; // 对应字节码 4: iload_1, 5: iload_2, 6: iadd, 7: istore_3
}
// 对应字节码 8: return
}
2
3
4
5
6
7
8
9
使用 javap -v StackStruTest.class
命令反编译得到的字节码(部分):
// class 文件名: StackStruTest.class
// ... (省略其他信息)
public static void main(java.lang.String[]);
// 方法描述符: 接收一个String数组参数,返回void
descriptor: ([Ljava/lang/String;)V
// 访问标志: public, static
flags: ACC_PUBLIC, ACC_STATIC
// 方法体代码
Code:
// 操作数栈最大深度为2, 局部变量表大小为4, 参数个数为1 (args)
stack=2, locals=4, args_size=1
// 字节码指令 (偏移量: 指令)
0: iconst_2 // 将int常量2压入操作数栈
1: istore_1 // 将栈顶int值(2)存入局部变量表索引1 (变量i)
2: iconst_3 // 将int常量3压入操作数栈
3: istore_2 // 将栈顶int值(3)存入局部变量表索引2 (变量j)
4: iload_1 // 将局部变量表索引1 (i=2) 的值压入操作数栈
5: iload_2 // 将局部变量表索引2 (j=3) 的值压入操作数栈
6: iadd // 将栈顶两个int值(2和3)相加,结果(5)压回栈顶
7: istore_3 // 将栈顶int值(5)存入局部变量表索引3 (变量a)
8: return // 方法返回 (void)
// 行号表: 关联字节码偏移量和源代码行号
LineNumberTable:
line 9: 0 // 源代码第9行对应字节码偏移量0开始
line 10: 2 // 源代码第10行对应字节码偏移量2开始
line 11: 4 // 源代码第11行对应字节码偏移量4开始
line 12: 8 // 源代码第12行对应字节码偏移量8开始 (隐式return)
// 局部变量表: 描述方法中局部变量信息
LocalVariableTable:
// 起始PC 作用范围长度 槽位索引 变量名 类型描述符
Start Length Slot Name Signature
0 9 0 args [Ljava/lang/String; // args变量,作用域0-8,槽位0
2 7 1 i I // i变量,作用域2-8,槽位1
4 5 2 j I // j变量,作用域4-8,槽位2
8 1 3 a I // a变量,作用域8-8,槽位3
// ... (省略其他信息)
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
29
30
31
32
33
34
35
36
37
提示
通过反编译字节码,我们可以清晰地看到Java源代码是如何被编译器转换成JVM指令的,有助于深入理解代码在底层基于栈的执行方式。
# 14. 为何选择基于栈的架构?
JVM选择基于栈的指令集架构,主要是为了跨平台性。因为不同硬件平台的CPU架构(寄存器数量、种类)差异很大,如果设计成基于寄存器的架构,就需要为每种平台进行适配和编译,违背了“一次编译,到处运行”的初衷。
基于栈架构的优缺点总结:
- 优点:
- 跨平台性强: 不依赖特定硬件寄存器。
- 指令集小: 实现简单,编译器开发更容易。
- 缺点:
- 指令数量多: 完成相同任务需要更多指令。
- 执行性能相对较低: 频繁的栈操作可能导致比寄存器操作更慢。
虽然现在JVM的主要运行环境已不再是资源极其受限的嵌入式平台,但基于栈的架构由于其良好的跨平台特性而被保留下来。同时,现代JVM通过 即时编译(JIT) 等技术,将热点代码编译成本地相关的、基于寄存器的优化指令,极大地弥补了纯解释执行栈指令的性能劣势。
# 15. JVM的生命周期
JVM的生命周期与其承载的Java程序的生命周期紧密相关。
# 虚拟机的启动
- Java虚拟机的启动是通过**引导类加载器(Bootstrap Class Loader)**创建一个初始类(Initial Class)来完成的。
- 这个初始类通常是包含
main
方法的那个类,由用户在命令行指定(例如java MyMainClass
)。 - 启动过程还包括JVM自身的初始化,如内存区域的分配、系统属性的设置等。
# 虚拟机的执行
- 一个运行中的JVM实例的核心任务就是执行Java程序。
- 当程序开始执行时(即
main
方法被调用时),JVM开始运行。 - JVM内部有多个线程在并发执行:执行
main
方法的主线程,以及其他由应用程序创建的线程,还有JVM内部的系统线程(如垃圾回收线程)。 - 严格来说,我们执行一个Java程序时,操作系统层面真正运行的是一个Java虚拟机进程。
# 虚拟机的退出
JVM进程会在以下几种情况下终止运行:
- 程序正常结束: 程序执行完成,所有非守护线程(Non-Daemon Threads)都已终止。例如,
main
方法执行完毕。 - 异常终止: 程序在运行过程中遇到未捕获的异常或错误(Error),导致某个线程或整个程序终止。
- 操作系统错误: 由于操作系统出现错误(如资源耗尽、强制关闭等),导致JVM进程被终止。
- 显式退出调用: 程序中某线程调用了
Runtime
类或System
类的exit()
方法,并且安全管理器允许该操作。或者调用了Runtime
类的halt()
方法(强制退出,不运行关闭钩子)。 - JNI调用: 通过Java Native Interface (JNI) 的 Invocation API 来卸载JVM。
# 16. JVM发展历程:群雄逐鹿
JVM并非只有HotSpot一种实现,历史上和现在都存在多种不同的JVM实现,各有特点和侧重。
# Sun Classic VM
- 年代: 1996年,随Java 1.0发布,是世界上第一款商用Java虚拟机。
- 特点:
- 内部只提供解释器,不包含即时编译器(JIT)。字节码完全靠解释执行,效率较低。
- 可以通过外挂方式使用JIT,但一旦启用JIT,解释器就不再工作,两者不能协同。
- 结局: 在JDK 1.4时被完全淘汰,其代码后来被整合进HotSpot VM(作为一种备用或调试模式)。
# Exact VM
- 年代: JDK 1.2时期,Sun公司为解决Classic VM性能问题而推出。
- 特点:
- 引入精确内存管理(Exact Memory Management):虚拟机可以准确知道内存中某个位置的数据类型,这是现代GC的基础。
- 具备现代高性能虚拟机的雏形:引入热点探测技术,编译器与解释器混合工作模式。
- 结局: 仅在Solaris平台上短暂使用过,很快被更优秀的HotSpot VM取代。
# HotSpot VM
- 历史:
- 最初由Longview Technologies公司设计。
- 1997年被Sun收购,2009年随Sun被Oracle收购。
- 从JDK 1.3开始成为默认虚拟机,并延续至今。
- 地位: 目前市场占有率绝对领先的虚拟机。无论是JDK 6、JDK 8、JDK 11还是更新的版本,默认都是HotSpot VM。Oracle JDK和OpenJDK都使用它。
- 核心技术:
- 热点代码探测(HotSpot): 通过计数器(方法调用计数器和回边计数器)找出频繁执行的代码(热点代码)。
- 解释器与编译器(Client C1 / Server C2)协同工作: 启动时解释执行,快速响应;对热点代码进行JIT编译优化,提升长期性能。在响应时间和吞吐量之间取得良好平衡。
- 先进的垃圾收集器: 提供了多种GC算法(Serial, Parallel, CMS, G1, ZGC, Shenandoah)以适应不同场景。
- 应用: 覆盖服务器、桌面、移动端、嵌入式等各种领域。本系列内容主要基于HotSpot VM进行讲解。
# JRockit VM
- 开发者: BEA Systems(后被Oracle收购)。
- 定位: 专注于服务器端应用,对启动速度要求不高,更看重长时间运行的性能和稳定性。
- 特点:
- 内部不包含解释器,所有代码都通过JIT编译后执行。
- 曾被认为是世界上最快的JVM之一,在许多基准测试中表现优异。
- 提供JRockit Real Time版本,用于低延迟应用(金融、电信等)。
- 提供Mission Control监控和分析工具套件。
- 结局: 被Oracle收购后,其优秀特性(如Mission Control)被逐步整合到HotSpot VM中。JRockit本身已停止独立发展。
# IBM J9 VM
- 全称: IBM Technology for Java Virtual Machine (IT4J),内部代号J9。
- 开发者: IBM。
- 定位: 与HotSpot类似,覆盖服务器、桌面、嵌入式等多用途。广泛用于IBM的WebSphere等产品中。
- 特点:
- 也是一款高性能商用虚拟机,同样号称是“世界上最快的Java虚拟机”之一。
- 在内存占用和启动速度方面通常优于HotSpot。
- 现状: 2017年,IBM将J9开源,命名为Eclipse OpenJ9,交由Eclipse基金会管理。成为OpenJDK之外的一个重要开源JVM选择。
# KVM / CDC / CLDC HotSpot
- 开发者: Oracle (源自Sun)。
- 定位: 面向**Java ME(Micro Edition)**平台,用于资源受限的移动设备和嵌入式系统。
- CLDC (Connected Limited Device Configuration): 针对内存、处理能力非常有限的设备(如功能手机、传感器)。其上的虚拟机早期是KVM (Kilobyte VM),特点是简单、轻量、可移植。
- CDC (Connected Device Configuration): 针对计算能力稍强的设备(如智能盒子、车载系统)。其上的虚拟机是基于HotSpot技术裁剪和优化的版本。
- 现状: 随着智能手机被Android和iOS主导,Java ME的市场份额萎缩。但在某些特定的低端嵌入式领域仍有应用。
# Azul VM / Zing VM
- 开发者: Azul Systems。
- 特点:
- Azul VM(早期): 是与特定硬件(Azul Vega系统)绑定的软硬件结合方案,号称“高性能JVM中的战斗机”。能管理超大内存(数百GB甚至TB级),并提供可控的、极低暂停时间的垃圾收集器(C4 GC - Continuously Concurrent Compacting Collector)。
- Zing VM(现在): 是Azul Systems推出的纯软件解决方案,可以在通用的x86硬件和云平台上运行。它将Azul VM的很多优势(特别是低延迟GC)带到了标准硬件上,主要面向对延迟极其敏感的金融交易、实时分析等领域。价格昂贵。
# Liquid VM (JRockit VE)
- 开发者: BEA Systems。
- 特点: 是一款直接运行在Hypervisor(虚拟机管理程序)之上的JVM,不需要底层操作系统。它自身实现了操作系统的必要功能(线程调度、文件系统、网络等),是一种专有虚拟机。
- 结局: 随着JRockit停止开发,Liquid VM项目也随之终止。
# Apache Harmony
- 开发者: Apache软件基金会(主要由Intel和IBM支持)。
- 目标: 提供一个与JDK 1.5/1.6兼容的开源Java运行平台。
- 特点: 包含了JVM实现(DRLVM)和Java类库。
- 结局: 由于受到OpenJDK的竞争压力,并且未能获得Sun公司的JCP(Java Community Process)兼容性认证,项目最终于2011年终止。其部分类库代码被Android项目吸收。
# Microsoft JVM
- 开发者: 微软。
- 背景: 为了在其IE3浏览器中支持Java Applets而开发。
- 特点: 只能在Windows平台运行,但据称是当时Windows下性能最好的JVM。
- 结局: 1997年,Sun公司以侵犯商标和不正当竞争为由起诉微软并胜诉。微软最终在其Windows XP SP3更新中移除了Microsoft JVM。现在Windows上运行Java都需要安装来自Oracle、OpenJDK社区或其他厂商的JVM。
# Taobao VM / AliJDK
- 开发者: 阿里巴巴JVM团队。
- 背景: 基于OpenJDK深度定制,以满足阿里在电商、金融、物流等复杂业务场景下对高并发、高可用、高性能的极致需求。
- 特点(部分):
- GCIH (GC Invisible Heap) 技术: 实现堆外内存(Off-Heap),将生命周期长的对象移出Java堆,由GCIH管理,降低GC频率和停顿时间。对象可在多JVM进程间共享。
- 优化JNI调用开销。
- 增强的诊断和性能分析工具。
- 针对大数据场景的GC优化(如ZGC的早期探索和应用)。
- 现状: AliJDK作为阿里巴巴内部Java体系的基石,广泛应用于其线上系统。部分技术(如Dragonwell JDK)已开源。
# Dalvik VM / ART VM
- 开发者: Google。
- 应用: Android操作系统。
- Dalvik VM (早期Android版本):
- 不是严格意义上的Java虚拟机,因为它不直接执行Java字节码(
.class
文件),也不遵循JVM规范。 - 执行的是DEX (Dalvik Executable) 格式的文件,该文件由Java字节码通过
dx
工具转换而来。 - 采用基于寄存器的架构,理论上指令数更少,执行效率可能更高。
- 早期版本主要是解释执行,Android 2.2引入了JIT。
- 不是严格意义上的Java虚拟机,因为它不直接执行Java字节码(
- ART VM (Android RunTime, Android 5.0及以后):
- 取代了Dalvik VM。
- 引入AOT (Ahead-Of-Time) 编译:在应用程序安装时或系统空闲时,将DEX字节码直接编译成本地机器码。运行时直接执行本地代码,启动更快,执行效率更高。
- 也支持JIT作为补充。
- 改进了垃圾回收机制。
# GraalVM
- 开发者: Oracle Labs。
- 发布: 2018年公开。
- 定位: 高性能、跨语言的全栈虚拟机,目标是“Run Programs Faster Anywhere”,被视为下一代JVM的有力竞争者。
- 核心特点:
- 基于HotSpot VM增强: 兼容现有的Java生态。
- 多语言支持(Polyglot): 不仅可以运行Java、Scala、Kotlin等JVM语言,还可以通过Truffle框架支持JavaScript, Node.js, Python, Ruby, R, C/C++, WebAssembly等多种语言。
- 语言间互操作: 支持在不同语言之间无缝调用对方的接口和对象。
- Graal编译器: 一个用Java编写的高性能动态编译器,可以作为HotSpot的顶级JIT编译器(替代C2),也可以用于其他语言的编译优化。
- Native Image: 可以将Java应用程序预编译(AOT)成本地可执行文件,实现秒级启动和极低的内存占用,特别适合云原生和Serverless场景。
- 潜力: 被认为是未来可能取代HotSpot的重要技术,但目前仍在发展中,生态和成熟度是关键。
# 总结
JVM的具体实现(包括内存结构细节、GC算法、JIT策略等)会因厂商和版本的不同而有所差异。学习JVM时,通常以Oracle HotSpot VM作为基准进行理解,同时了解其他重要JVM(如OpenJ9, GraalVM)的特点和差异,有助于拓宽视野。