虚拟机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文件。它破坏了虚拟机一次编译处处运行的特色,不过这玩意儿不常用,了解就好。
