JDK、JVM的迭代与演进
Java的演进是JDK语言特性与JVM底层技术协同发展的历程,每次迭代都是为了解决当时的性能瓶颈、开发效率或应用场景的挑战。
一. JDK 语言与JVM发展史
- JDK 1.0 (1996):引入Java语言、Applet和AWT,JVM为Sun Classic VM。
- JDK 1.1 (1997):新增JDBC、内部类、反射、JAR文件和JIT编译器。
- JDK 1.2 (1998):引入Swing、集合框架、JIT编译器;Java更名为Java 2。
- JDK 1.3 (2000):HotSpot成为默认虚拟机,性能大幅提升。
- JDK 1.4 (2002):引入NIO、正则表达式、断言、内置XML解析器。
- JDK 5 (2004):革命性更新,引入泛型、注解、枚举、自动装箱/拆箱。
- JDK 6 (2006):JDBC 4.0、JConsole监控工具,Sun宣布Java开源。
- JDK 7 (2011):Oracle收购后的首个大版本,G1收集器引入、菱形语法。
- JDK 8 (2014):LTS版本,Lambda/Stream API革命,用元空间(Metaspace)取代永久代(PermGen)。
- JDK 9 (2017):模块化系统(Project Jigsaw)。
- JDK 10 (2018):局部变量类型推断(
var)、JIT编译器增强。 - JDK 11 (2018):LTS版本,HTTP Client(标准)、ZGC并发垃圾回收器、TLS 1.3。
- JDK 12-16 (2019-2021):Switch表达式(预览/正式)、文本块、记录类、模式匹配(预览)。
- JDK 17 (2021):LTS版本,密封类、增强伪随机数生成器、ZGC性能大幅提升。
- JDK 18-20 (2022-2023):UTF-8默认字符集、虚拟线程(预览)、结构化并发(预览)。
- JDK 21 (2023):LTS版本,正式发布虚拟线程、分代ZGC、记录模式、字符串模板(预览)。
- JDK 22+ (2024-至今):持续优化虚拟线程、结构化并发,不断提升性能与新特性。
⚙️ JVM 底层核心演进解析
- 内存模型:JVM内存管理实现了从“永久代 (PermGen)”到“元空间 (Metaspace)”的革新。
- 存在的问题:永久代内存空间固定且属于JVM堆的一部分,频繁动态加载类易导致
OutOfMemoryError: PermGen space,且内存回收会增加Full GC复杂性。 - 解决的方案 (JDK 8):移除永久代,引入元空间。元空间使用操作系统本地内存,内存大小仅受系统限制,从根本上解决了内存溢出问题,并解耦了元数据回收与GC,提升了整体性能。
- 存在的问题:永久代内存空间固定且属于JVM堆的一部分,频繁动态加载类易导致
- 垃圾回收:JVM垃圾回收器演进始终在吞吐量、延迟和内存占用三者间寻求最优平衡。
- Serial:JDK 1.3前默认,简单单线程,但会引发STW。
- Parallel:JDK 1.4引入,多线程并行提升吞吐量。
- CMS:JDK 1.4时期,目标是减少停顿,但易产生内存碎片且CPU敏感。
- G1:JDK 7u4引入,JDK 9成为默认收集器。目标平衡吞吐量和延迟,优先回收垃圾最多的区域。
- ZGC:JDK 11引入,JDK 21支持分代模式。目标为TB级堆内存提供亚毫秒级停顿,暂停时间不随堆增大而增加。
- 执行引擎:JVM核心执行引擎始终围绕解释器与即时编译器 (JIT) 协同工作,通过热点代码探测,将高频执行的字节码动态编译为本地机器码,大幅提升运行时性能。JDK 10还引入JVMCI,允许用Java编写编译器插件,加速GraalVM等新技术的集成。
💡 容器化与云原生改进 (JDK 8u191 / JDK 10+)
早期JVM无法感知容器(如Docker)的CPU和内存限制,只能看到宿主机的全部资源,导致容器资源隔离机制失效。JDK 8u191及JDK 10开始,JVM可感知容器分配的CPU和内存限制,避免因资源争抢被宿主机强制终止,确保在云原生环境中的稳定运行。
二.不同JVM的优缺点
为了更准确地满足你的需求,需要先明确一下:“每个JVM”通常指的是不同的JVM实现(比如Sun/Oracle HotSpot、BEA JRockit、IBM J9等),而不是JDK版本或垃圾回收器。因为JVM作为规范,有多种商业和开源实现,它们各有独特的设计目标和优缺点。
下面列出主流及历史上重要的JVM实现,包含其出现的历史背景、优点、缺点。
1. Sun Classic VM(JDK 1.0/1.1 默认)
- 历史背景:Java诞生初期的官方虚拟机,只为“跑起来”,性能并非首要目标。
- 优点:
- 简单、稳定,奠定了JVM基础架构。
- 缺点:
- 只有解释器,无JIT编译器,执行效率极低。
- 垃圾回收只有简单的串行GC,停顿时间长。
2. Exact VM(JDK 1.2 时的实验性VM)
- 历史背景:Sun为下一代高性能JVM做的尝试,试图解决Classic VM的性能问题,但后来被HotSpot取代。
- 优点:
- 引入精确内存管理(准确式GC),能区分内存中哪些是指针、哪些是值。
- 初步支持JIT编译。
- 缺点:
- 未成熟就夭折,功能不完整。
- 生态支持差,很快被HotSpot替代。
3. HotSpot VM(目前最主流,JDK 1.3 至今)
- 历史背景:由Longview Technologies开发,1997年被Sun收购,从JDK 1.3开始成为默认JVM。其名字来源于热点代码探测技术。
- 优点:
- 混合执行模式(解释器 + 多层JIT编译),长时间运行性能优秀。
- 强大的GC家族(Serial/Parallel/CMS/G1/ZGC/Shenandoah),适用场景广。
- 成熟稳定,社区庞大,文档丰富。
- 持续演进(Lambda、模块化、虚拟线程等)。
- 缺点:
- 启动时间和内存占用相对较重(相比某些轻量JVM)。
- 某些极端低延迟场景不如专门优化的JVM(如Azul Zing)。
4. BEA JRockit(专注于服务器端,被Oracle收购)
- 历史背景:BEA公司开发,主打高性能服务器应用(WebLogic等),JDK 6时代与HotSpot竞争激烈。Oracle收购BEA后,将其特性融入HotSpot。
- 优点:
- 不包含解释器,纯JIT编译,启动后峰值性能极高。
- 独创的动态自适应编译和确定性GC,延迟可控。
- 自带MissionControl(飞行记录器)等高级诊断工具。
- 缺点:
- 启动慢,内存占用大。
- 无法在ARM等平台运行,生态较窄。
- 已被Oracle合并到HotSpot(部分特性如JFR、改进的GC算法被吸收)。
5. IBM J9 VM(IBM SDK, Technology for Java)
- 历史背景:IBM为自家AIX、Linux on Power、z/OS及通用x86平台开发的JVM,强调资源节约和跨平台一致性。后来捐赠给Eclipse基金会,演化为OpenJ9。
- 优点:
- 内存占用极低(通过共享类缓存、紧凑对象指针等技术)。
- 启动速度快,适合云原生和容器环境。
- 出色的吞吐量和中等延迟(基于均衡GC策略)。
- 缺点:
- 相对于HotSpot,在某些纯计算密集型场景峰值性能略低。
- 社区规模和历史资料不如HotSpot丰富。
6. Azul Zing(基于C4垃圾回收器)
- 历史背景:Azul Systems公司专为低延迟、大堆内存(数百GB到TB级)场景设计的商业JVM,核心是无停顿C4收集器(Continuously Concurrent Compacting Collector)。
- 优点:
- 极致低延迟:GC停顿通常不超过1-2毫秒,且不随堆大小增长。
- 超大堆内存稳定:可处理TB级堆,无Full GC问题。
- ReadyNow技术消除预热期,启动即达峰值性能。
- 缺点:
- 商业软件,需付费授权。
- 普通开发环境极少使用,主要用于金融、电商等苛刻场景。
7. GraalVM(Oracle新世代多语言VM)
- 历史背景:Oracle开发的高性能、多语言(Java/JS/Python/R/等)虚拟机,支持提前编译(AOT)和即时编译(JIT),目标是替代HotSpot的JIT编译部分。
- 优点:
- 多语言无缝互操作(Polyglot)。
- 支持Native Image(AOT编译成独立可执行文件),启动毫秒级、内存占用极小。
- 高性能JIT(Graal JIT编译器)在某些场景超过C2。
- 缺点:
- Native Image对反射、动态代理等动态特性支持有限,需要配置。
- 生态尚在追赶HotSpot,部分框架兼容性需调试。
- 构建镜像时间较长。
8. 其他JVM
- Microsoft JVM:微软早期为IE支持的Java实现,因法律纠纷停止,无现代影响。
- KVM / CVM:用于嵌入式/功能手机(如Java ME),性能极简,现已淘汰。
总结建议
- 日常开发/服务器:HotSpot(最通用,JDK 11/17/21 LTS)。
- 低内存、快速启动(云原生):OpenJ9。
- 超低延迟、超大堆(金融/交易):Azul Zing(商业)或 HotSpot + ZGC(免费)。
- 多语言、AOT原生编译:GraalVM。
三. HotSpot的垃圾回收器有哪些,出现的历史背景,各自的优缺点
从Java初生到现代云原生,HotSpot虚拟机中的垃圾回收器(GC)演进史,正是一部在吞吐量(单位时间处理能力)和延迟(单次响应速度)这对核心矛盾中不断寻求平衡和突破的历史。下面这张全景图可以帮你快速建立整体认知:
⚙️ 核心演进脉络
- 吞吐量优先(JDK 1.4 - JDK 8):在早期多核CPU普及阶段,Parallel Scavenge + Parallel Old 组合作为JDK 8的默认收集器,通过并行化高效利用CPU资源,最大化程序运算吞吐量。
- 低延迟探索(JDK 1.4.1 - JDK 9):为满足交互式应用需求,划时代的CMS作为首个并发收集器被引入,使GC线程与用户线程并发工作以减少停顿。但因其碎片化和CPU敏感等问题,JDK 9开始被更先进的G1取代,CMS被标记为弃用。
- 现代低延迟与云原生(JDK 7u4 - 至今):G1在JDK 9成为默认收集器,其基于Region的布局和对大堆的平衡能力是关键。为应对更极端的延迟和内存要求,JDK 11起引入了ZGC和Shenandoah,实现了亚毫秒级停顿,并持续优化以适应云原生环境。
⚙️ 主流垃圾回收器全景解析
为了更深入地理解,这里为你梳理了HotSpot历史上及当前主要的垃圾回收器:
📜 经典世代:吞吐量与并发初探
- Serial / Serial Old:JDK 1.3前的新生代唯一选择,开创性基础收集器,但单线程工作会导致较长的STW停顿。
- ParNew:Serial的多线程并行版本,作为新生代收集器曾长期与CMS配合,但JDK 9后逐渐淡出。
- Parallel Scavenge / Parallel Old:JDK 8默认收集器,多线程并行以最大化吞吐量,适合后台运算任务,但无法避免GC时的全局暂停。
- CMS (Concurrent Mark Sweep):JDK 1.4.1引入,旨在获取最短回收停顿时间。采用并发收集机制,但会产生内存碎片并可能发生并发模式失败。
🚀 现代世代:平衡与极致低延迟
- G1 (Garbage First):JDK 7u4引入,JDK 9成为默认服务端收集器。开创性地基于Region内存布局和停顿预测模型,旨在平衡吞吐量和延迟。优势在于并行与并发兼顾、可预测的停顿时间以及空间整合,但不适合堆内存较小(<6GB)的场景,且维护记忆集有一定开销。
- ZGC (Z Garbage Collector):JDK 11引入(实验),JDK 15生产可用。设计目标为亚毫秒级停顿且停顿时间不随堆大小增长。采用并发处理、染色指针和读屏障等技术,适用于超大堆内存(TB级别),但会牺牲部分吞吐量(约5%-15%)。
- Shenandoah:JDK 12引入(实验),JDK 15生产可用。与ZGC定位类似,但由RedHat开发维护(仅限OpenJDK)。亮点在于支持并发的对象整理,能提供与ZGC相近的低延迟,但使用Brooks Pointer和多重屏障导致额外的内存和CPU开销。
四. JIT出现的历史背景及作用
JIT(Just-In-Time,即时编译)是Java实现高性能运行的核心技术。要理解它的作用,需要先回到Java诞生时的执行模式。
📜 历史背景:从“一次编译,到处运行”的困境说起
- 1996年,JDK 1.0:Sun发布的Java虚拟机(Sun Classic VM)采用纯解释执行。JVM逐条读取字节码并翻译成机器码执行,虽然实现了跨平台,但执行效率远低于C/C++等提前编译(AOT)的语言。Java因此被嘲讽“运行缓慢”。
- 早期解决尝试:JDK 1.1引入了JIT编译器,但当时的JIT会在方法首次调用时将其全部编译为本地代码。这导致两个问题:
- 启动时间极长:所有方法无论执行频率都编译,大型应用启动变慢。
- 内存占用大:编译后的机器码占用了大量内存。
- 转折点:1997年,Sun收购了Longview Technologies公司,获得了HotSpot技术。该技术基于热点探测(找出高频执行代码)的思想,只在必要时编译,从而兼顾启动速度和峰值性能。从JDK 1.3开始,HotSpot成为默认JVM,并沿用至今。
⚙️ JIT 的核心作用
JIT是JVM执行引擎的一部分,它在运行时将部分字节码动态编译为本地机器码,并缓存起来供后续使用。主要作用包括:
-
大幅提升运行性能
- 解释执行:每次执行都需要翻译,效率低。
- JIT编译后:直接执行本地机器码,速度可接近纯AOT编译语言(如C++)。
-
利用动态信息进行激进优化
- 静态编译器(如GCC)无法获知运行时信息。
- JIT可以收集方法调用频率、分支命中率、实际类型等数据,执行:
- 方法内联:将小方法直接嵌入调用者,消除调用开销。
- 逃逸分析:如果对象不会逃逸出线程或方法,可以栈上分配或标量替换,减少堆压力。
- 锁粗化/消除:对不必要的同步操作进行优化。
- 虚方法优化:通过类型分析将虚方法调用转为直接调用(去虚化)。
-
降低启动与内存开销(相比传统AOT或全量JIT)
- 现代HotSpot使用分层编译:先由解释器快速启动,然后C1编译器做简单优化(低开销),C2编译器对热点代码做深度优化。
- 这实现了快速启动 + 长时间运行后达到峰值性能的平衡。
🧠 典型工作流程(以HotSpot为例)
- 程序启动 → 解释执行(无编译成本)。
- JVM收集执行计数(方法调用、循环回边)。
- 当计数超过阈值 → 编译任务提交到后台编译线程。
- 编译完成后,代码入口从解释版本切换到编译版本。
- 随着程序运行,更热的代码会被更高级的编译器(如C2)重新优化编译。
📊 效果对比(示意数据)
| 模式 | 启动时间 | 峰值性能 | 内存占用 |
|---|---|---|---|
| 纯解释执行 | 最快 | 极低 | 最低 |
| 全量JIT(早期) | 极慢 | 接近C++ | 高 |
| 现代HotSpot分层JIT | 较快 | 接近C++ | 中等 |
🌐 延伸:JIT与AOT之争
- 传统JIT优势:自适应优化(利用运行时反馈)、跨平台(字节码一次打包)。
- 现代AOT(如GraalVM Native Image):启动毫秒级、内存占用极小,但丢失动态优化(如内联多态)且反射支持受限。
- 混合方案:JDK中已引入提前编译缓存(如AppCDS、Class Data Sharing),以及即时编译与AOT相结合的尝试(Project Leyden,Java 23+预览)。
💎 总结
JIT的出现,本质上是解决Java跨平台解释执行效率低下的痛点。它通过运行时动态编译热点代码,使得Java在保持“Write Once, Run Anywhere”能力的同时,获得了接近甚至局部超越传统静态编译语言的性能。可以说,没有JIT,Java不可能在服务器端和高性能计算领域取得今日的地位。
更多推荐


所有评论(0)