虚拟机1-宏观视角
# JVM
用文字的形式编写JVM系列博客,之后可以做成音频的方式整理,希望我们的虚拟机之旅依然有趣
# JVM有啥用
今天咱们聊聊 Java 虚拟机,也就是 JVM,以及它怎么支持混合语言编程。一句话说完就是 jvm 是中间语言的执行平台, jvm 是中间语言的执行平台, jvm 是中间语言的执行平台。这是 jvm 的核心
# 什么是 JVM?
首先,JVM 不是我们平时说的虚拟主机,而是一个编程平台。它的牛处在于跨平台——你写好 Java 代码,用编译器生成 .class 字节码文件,JVM 把它加载到内存,再翻译成操作系统能懂的指令,程序就跑起来了。 想象一下,JVM 就像个翻译官,不管你用什么语言喊话,只要翻译成它认识的字节码,它就能帮你执行。所以,JVM 最早是为 Java 设计的,但现在它已经不只服务 Java 了。我们现在只要把它想象成是字节码的通用运行平台即可。也就是它最早是 Java 语言的专用平台,现在它的拓展性更强了,它就是字节码(中间语言)的通用运行平台。
# 那么,混合语言编程是怎么回事?
重点来了:JVM 只认字节码,跟你用什么语言写代码没关系。字节码有个标准格式,只要符合要求,任何语言都能生成它。
- 比如,你写 Java 代码,编译成字节码;换成 Groovy 或者 Kotlin,也能通过它们自己的编译器生成一样的 .class 文件。JVM 加载后,转成机器码,运行效果跟用 Java 语言写出来的程序没差别。
- 这就有点像做菜,不管你用的是中式,印度菜还是西式食材,只要按标准切好,JVM 这台“厨师机”都能烹饪出结果。
# 常见的 JVM 语言有哪些?
现在,基于 JVM 的语言越来越多,咱们挑几个常见的说说:
- Java 最经典,用的人最多,招聘市场上至少一半岗位都跟它有关。虽然不是最潮的,但稳得一批。
- Kotlin 谷歌推的,安卓开发的宠儿。语法更现代,但底层字节码跟 Java 一样。
- Groovy 脚本语言,像 JavaScript 那样简单,能快速开发。
- Scala 大数据领域的明星,跟 Spark 框架是绝配。
# 核心原理
这些语言看着不一样,但生成的字节码结构是一样的。JVM 不管你是 Java 还是 Scala,它只管加载字节码,再根据你的操作系统转成机器码运行。这就是混合语言编程的底层逻辑。
# 总结
所以,JVM 不再是 Java 的专属,以前 JVM 只做 Java 这道菜,现在他支持更多食材了,什么材料拿过来,只要有对应的编译器帮我生成标准的字节码,JVM 都能帮你完美运行。
# JVM的发展历程
# 谁有资格造 JVM?
先说清楚,JVM 不是某个公司开发的一个固定产品,而是一套规范。只要你按这套规则做出来,并且通过 Oracle 的 TCK 测试——就是一大堆测试用例——你的作品就能叫 JVM。所以,JVM 不只有 Oracle 一家在做。
几年前,Oracle 把 JDK11 商业化,很多人慌了,觉得 Java 要完蛋。其实大可不必担心,因为 JVM 有开源版,也有其他厂商的版本,Java 的路还长着呢。
# JVM 的两大阵营
按厂商分,JVM 主要有两类:
- Sun(现在归 Oracle)开发的
- 其他厂商的产品
# Sun 的三种 JVM
Sun 开发了三款 JVM,按时间顺序递进:
Classic VM 最早的,从 Java 1.0 到 1.4 用过。它只有解释器,啥意思呢?
比如你写代码System.out.println("Hello World")
,编译成字节码后,解释器一行行翻译成 CPU 能懂的机器码,马上执行。好处是响应快,但效率低。为啥?
假设你有 100 行一样的System.out
,它得翻译 100 次,没缓存,太浪费。所以后来有了改进。Exact VM Java 1.2 推出的,里程碑式变化。它把解释器和即时编译器(JIT)混合用了。
JIT 是啥?简单说,它能找出代码里的“热点”——比如那 100 行System.out
——提前翻译成机器码缓存起来,下次直接用,不用重复翻译。这样既快,又高效。
可惜,Exact VM 只在 Sun 的 Solaris 系统上跑,其他系统还得用老的 Classic VM,市场太小,很快就淘汰了。但它的混合设计理念被继承下来。HotSpot VM Java 1.3 开始用的,现在最主流,覆盖 JDK 1.3 到 Java 14。
HotSpot 不是 Sun 原创,是 Longview 公司设计的,后来被 Sun 收购。它完美实现了解释器和 JIT 的混合运行,能在响应速度和执行效率间找平衡。最关键的是,它跨平台,哪儿都能用。
HotSpot 还有个特点:有方法区,存类信息,早叫“永久代”,总之,它是现在每个 Java 程序员都得懂的 VM。有个小细节:方法区是 JVM 规范的一部分,Classic VM 和 Exact VM 也有,只是实现不同。HotSpot 把它叫“永久代”是个特色,但不是独有。
# 其他厂商的 JVM
刚刚我们讲到了Sun公司(后来被Oracle收购)的JVM演变,从Classic VM到Exact VM,再到现在的HotSpot VM,这些都是Sun自己研发或整合的。但JVM不只有Sun一家在做,全球还有很多厂商开发自己的JVM,各有特色。今天挑三个有代表性的讲讲:JRocket VM、IBM J9 VM和淘宝VM,再顺带聊聊OpenJDK和Oracle JDK的区别。
# JRocket VM:服务器端速度王
先说JRocket VM,BEA公司开发,2008年被Oracle收购。它号称“全球最快JVM”,没之一。经过评测,它执行效率比主流JVM高70%,尤其在服务器硬件优化上很强。为什么快?因为它定位明确,只做服务器端JVM,不像HotSpot要兼容个人开发、企业应用等各种场景。
JRocket有个大胆取舍:**去掉解释器,只用编译器。**解释器响应快,但效率低;编译器通过热点探测能极大提升执行效率。所以JRocket启动慢,但跑起来特别快。这对服务器很友好,启动时间不敏感,但运行效率关键。
# IBM J9 VM:蓝色巨人的自嗨
再看IBM的J9 VM,全称IBM Technology for JVM,内部代号J9。它跟HotSpot定位类似,是通用型JVM,解释器和编译器都有。IBM号称它是最快的JVM,但评测显示,只有在IBM自家硬件上优化得好,换到其他平台就一般般。所以这“最快”有点自嗨成分。
J9还有个特点:一开始不开源,2017年才推出Open J9。相比JRocket的专精服务器,J9更像HotSpot,走通用路线,但在细分领域没那么突出。
# 淘宝VM:高并发定制
最后说说国内的淘宝VM,阿里巴巴基于OpenJDK HotSpot改造的,专为淘宝的高并发、高吞吐场景打造。它全面替换了Oracle JVM。测试数据很亮眼:年轻代垃圾回收速度比Oracle版低(数值越低越快),吞吐量也明显提升。淘宝VM就是个“专属定制款”,为自家业务量身定做。淘宝VM年轻代GC更快,是因为它针对高并发场景优化了算法、参数和硬件适配,减少了暂停时间。
# OpenJDK vs Oracle JDK
聊完第三方JVM,顺便说说OpenJDK和Oracle JDK的区别,因为淘宝VM就基于OpenJDK。两者都由Oracle开发,核心都是HotSpot VM,本质一样,但细节不同。
- 授权:Oracle JDK用JRL许可,只限个人研究,商用未经授权有法律风险。OpenJDK用GPLv2许可,允许商用,没这顾虑。
- 功能:Oracle JDK像“商业版”,功能全;OpenJDK是“开源版”,简化了些,比如没Deployment部署功能,边缘模块也删了些。但日常开发基本用不到这些,影响不大。
- 认证:OpenJDK最初没通过Oracle的TCK测试(全面验证JVM规范的用例集),不能用Java商标。后来红帽牵头补全,现在的OpenJDK已通过TCK,跟OracleJDK核心功能无差。
# 总结一下
JVM 从 Classic VM 的简单解释,到 Exact VM 的混合尝试,再到 HotSpot 的全面优化,走过了效率和兼容性的进化路。现在它不只是 Java 的舞台,其他厂商也在玩出花样。
大家记住:JVM 是规范,不是单一产品;HotSpot 是主流,但不是唯一。
我们除了SUN 公司开发的JVM,还有其他的这几个虚拟机,他们各有特色:JRocket专攻服务器速度,J9偏通用但IBM硬件优化最好,淘宝VM为高并发定制。HotSpot在通用型JVM里已经很优秀了,这些版本是在细分领域发力。OpenJDK和Oracle JDK区别主要是授权和功能范围,开发用OpenJDK足够。
# JVM结构组成
先来看JVM的整体结构,其实不复杂,分三大块:类加载子系统、运行时数据区和执行引擎。这三块就像一个团队,缺一不可。咱们按Java代码执行的顺序来拆解一下。
首先,我们写的都是.java
文件,也就是源代码。源代码要运行,得先用javac
命令编译成.class
文件,也就是字节码文件。.java
是文本文件,.class
是二进制文件。编译完之后,这些字节码文件怎么用呢?这就轮到JVM上场了。
JVM是Java的核心,它在操作系统之上建了一个独立运行的环境,跟外界依赖很少。所以,只要装了JVM,不管是Windows还是Linux,Java都能“一次编译,到处运行”。那JVM具体干了啥呢?咱们从三大块开始讲。
首先,我们从宏观的角度来去思考,虚拟机是怎样设计的。它是分为了三层架构,第一层是输入端,第二层是文件存储端。第三层也就是输出端,会直接接触到我们的 CPU 这一层。输入端的实现就是类加载子系统(负责将字节码文件转化为类对象的定义方法),文件存储层的实现就是我们的运行时数据区(负责存储我们的类,也就是我们的数据信息),而输出层的实现,就是我们的执行引擎(负责根据用户的操作执行对应的运算过程)
# [1]类加载子系统
类加载子系统看起来高大上,其实很简单。它的任务就是把编译好的.class
文件读进来。这些文件里包含了类的定义信息,比如方法、变量啥的。读进来之后,总得有个地方存吧?这就到了第二块——运行时数据区。
# [2]运行时数据区
运行时数据区就是JVM在内存里划出的一块地儿,专门存数据。类加载子系统把类的信息塞进来,程序运行时的中间结果也放这儿。按区域分,它有两种:线程共享区和线程私有区。
线程共享区:所有线程共用,里面又分两块:
- 堆:放你
new
出来的对象,是JVM里占内存最大的地方。如果程序报“内存溢出”(OOM),十有八九是堆不够用了。 - 方法区:存类的原始信息,类的模板信息,比如方法定义、常量啥的。你可以把它想象成类的“档案馆”。
- 堆:放你
线程私有区:每个线程独享,分三部分:
- 虚拟机栈:占空间最大,存方法调用的栈信息。比如A方法调用B,B调用C,这些调用关系都压在这儿,每个线程都有自己的栈。存储这些关系是为了能让方法执行完毕之后,能正常的回到上一个方法的调用位置。
- 程序计数器:就像个记号,告诉你程序执行到哪一行了,方便跳来跳去。
- 本地方法栈:跟虚拟机栈差不多,但它是给本地方法准备的。比如你调用了Windows的某个命令,这些执行过程就放这儿。不过提醒一下,能不用本地方法就别用,用了就跟操作系统绑死了,程序就没法随便移植了。但我们用到 AI 的时候,有些 AI 是用 Python 或者是用 c艹 来调用的。这个时候我们就会用到本地方法栈了 本地方法栈是存本地方法执行过程的内存区域,而本地方法接口是连接JVM和操作系统本地库的桥梁,俩功能不一样,别搞混了。
运行时数据区只是个仓库,存东西的地方。程序要计算怎么办?这就靠第三块——执行引擎了。
# [3]执行引擎
执行引擎是干活儿的。它从运行时数据区拿数据,交给CPU加工,再把结果存回去。字节码是JVM的语言,但CPU只认识01二进制。执行引擎就负责翻译,把字节码转成CPU能懂的指令,算完再存回内存。
# 整体流程
简单总结一下:类加载子系统负责输入,把字节码加载进来;运行时数据区负责存储,管好所有数据;执行引擎负责计算,跟CPU合作干活。这三块就组成了我们的虚拟机系统(发现这里跟大学时学的控制系统非常像)
对了,还有个小挂件叫本地方法接口,用来跟操作系统交互,比如调用Windows的DLL文件。它破坏了虚拟机一次编译处处运行的特色,不过这玩意儿不常用,了解就好。