
上一篇【第02篇】Java 虚拟机的工作原理——从 java HelloWorld 说起下一篇【第04篇】Go 语言环境准备——用 Go 写 JVM 的 5 大理由摘要要写一个能跑 Java 程序的虚拟机光有 Go 环境是不够的——你得先有 JDK。原因很实在我们需要 javac 把 Java 源码编译成 class 文件作为测试输入需要 javap 反汇编字节码用来验证我们的解析结果对不对还需要 JDK 自带的类库自己实现的 JVM 可没法重写整个 java.lang。本文带你完成搞清 JDK/JRE/JVM 三者关系、选定 JDK 8 版本、Windows/Mac/Linux 三平台安装、JAVA_HOME 与 PATH 配置、验证安装的三个测试最后重点介绍本系列出场率最高的两个工具——javac 和 javap。一、先搞清楚 JDK、JRE、JVM 是什么关系很多 Java 程序员干了几年还分不清这三个名词。先花两分钟理清楚【JDK / JRE / JVM 三者的关系】 ┌─────────────────────────────────────────────────────┐ │ JDK │ │ (Java Development Kit) │ │ Java 开发工具包 │ │ ┌───────────────────────────────────────────────┐ │ │ │ JRE │ │ │ │ (Java Runtime Environment) │ │ │ │ Java 运行时环境 │ │ │ │ ┌─────────────────────────────────────────┐ │ │ │ │ │ JVM │ │ │ │ │ │ (Java Virtual Machine) │ │ │ │ │ │ Java 虚拟机 │ │ │ │ │ │ · 类加载器 │ │ │ │ │ │ · 运行时数据区 │ │ │ │ │ │ · 执行引擎 GC │ │ │ │ │ └─────────────────────────────────────────┘ │ │ │ │ │ │ │ │ Java 核心类库 (java.lang / java.util / ...) │ │ │ └───────────────────────────────────────────────┘ │ │ │ │ 开发工具 (javac / java / javap / jar / jps / ...) │ └─────────────────────────────────────────────────────┘ 简单记JDK JRE 开发工具 JRE JVM 核心类库 JVM 执行引擎就是我们要写的那个东西名称全称用途谁需要装JVMJava Virtual Machine执行字节码我们要自己写一个JREJava Runtime Environment运行 Java 程序只需要跑 Java 程序的用户JDKJava Development Kit开发 运行 Java 程序开发者我们重点我们要写的 jvmgo对标的是图中最内层的JVM那一块。但我们依赖 JDK 的类库java.lang.Object、String、System 这些——自己实现的 JVM 不可能把整个 Java 类库重写一遍所以运行时还是要去加载 JDK 的 rt.jar。二、为什么是 JDK 8现在 JDK 都出到 21 了为什么本系列要用 JDK 8因为《自己动手写 Java 虚拟机》这本书基于《Java 虚拟机规范第 8 版》对应的正是 JDK 8。选它的理由有四条理由说明规范对应JVM Spec 第 8 版是最经典、被分析得最透彻的版本资料最全避开复杂特性JDK 8 之后引入了模块化JPMSJDK 9、invokedynamic 大量使用Lambda、以及新的类文件格式变化都会增加实现复杂度生态熟悉度JDK 8 至今仍是企业级应用的主力版本2026 年了依然如此读者更熟悉学会了不难迁移理解了 JDK 8 的 JVM 原理再看 JDK 11/17/21 的新特性G1 默认、ZGC、虚拟线程是水到渠成的事各大 JDK 版本与 class 文件主版本号的对应关系JDK 版本class 主版本号 (major_version)发布时间JDK 1.4482002JDK 5492004JDK 6502006JDK 7512011JDK 852← 我们用这个2014JDK 11 (LTS)552018JDK 17 (LTS)612021JDK 21 (LTS)652023重点这个版本号后面解析 class 文件时会用到。看到major_version 52就知道这是 JDK 8 编译的 class 文件。三、JDK 下载与安装三平台Windows访问 Oracle 官网 JDK 8 下载页或直接用国内镜像如华为云、清华镜像选择jdk-8uXXX-windows-x64.exe64 位双击安装一路下一步。建议安装路径不要有中文和空格例如C:\Java\jdk1.8.0_202安装过程中会再弹一次 JRE 安装默认即可提示Oracle 从 JDK 8u211 开始要求登录账号才能下载。嫌麻烦的话可以用 OpenJDK 的 Windows 版本如 Adoptium/Eclipse Temurin、Amazon Corretto、华为毕昇 JDK功能完全等价。macOS# 方式一用 Homebrew推荐brewinstall--casktemurin8# 方式二下载 dmg 安装包双击安装# 安装后 JDK 通常在 /Library/Java/JavaVirtualMachines/Linux# Ubuntu / Debiansudoaptupdatesudoaptinstallopenjdk-8-jdk# CentOS / RHELsudoyuminstalljava-1.8.0-openjdk-devel# 验证java-version四、配置 JAVA_HOME 与 PATH装完 JDK 还不够还得配两个环境变量。Windows右键此电脑 → 属性 → 高级系统设置 → 环境变量在系统变量里变量名变量值说明JAVA_HOMEC:\Java\jdk1.8.0_202指向 JDK 安装根目录PATH追加%JAVA_HOME%\bin让命令行能找到 java/javac配置完一定要重新打开命令行窗口已开的窗口不会加载新的环境变量然后验证C:\echo%JAVA_HOME% C:\Java\jdk1.8.0_202 C:\java-versionjavaversion1.8.0_202Java(TM)SE Runtime Environment(build1.8.0_202-b08)Java HotSpot(TM)64-Bit Server VM(build25.202-b08, mixed mode)macOS / Linux编辑 shell 配置文件.bashrc、.zshrc或.bash_profile# 加到 ~/.zshrc 或 ~/.bashrcexportJAVA_HOME/Library/Java/JavaVirtualMachines/temurin-8.jdk/Contents/HomeexportPATH$JAVA_HOME/bin:$PATH然后让配置生效source~/.zshrc# 或 source ~/.bashrcjava-version重点java -version输出的最后一行Java HotSpot(TM) 64-Bit Server VM就是当前使用的 JVM 实现。等我们写完 jvmgo就可以自豪地说我也有一个自己的 JVM 了。五、验证安装的三个测试环境配好后跑三个测试确认一切正常。测试 1java -versionjava-version能看到版本号输出说明 PATH 配置 OK。测试 2编译并运行 HelloWorld创建一个测试目录写个 HelloWorld// HelloWorld.javapublicclassHelloWorld{publicstaticvoidmain(String[]args){inta1;intb2;System.out.println(a b (ab));}}编译并运行javac HelloWorld.java# 生成 HelloWorld.classjavaHelloWorld# 输出a b 3测试 3javap 反汇编javap-c-vHelloWorld.class能看到字节码和常量池详情说明 JDK 完整可用。六、JDK 目录结构扫盲装完 JDK值得花两分钟看看它的目录结构——后面我们要频繁和这些目录打交道【JDK 8 目录结构】 $JAVA_HOME/ ├── bin/ ← 开发工具全在这儿 │ ├── java JVM 启动器我们要对标的就是它 │ ├── javac 编译器.java → .class │ ├── javap 反汇编器.class → 可读字节码 ★常用 │ ├── jar 打包工具 │ ├── jps / jstat 进程查看 / 性能统计 │ ├── jstack 线程快照 ★排障神器 │ ├── jmap 内存快照 ★排障神器 │ └── jconsole / jvisualvm 图形化监控 │ ├── jre/ ← 运行时环境 │ ├── bin/ │ │ └── java JVM 可执行文件 │ └── lib/ │ ├── rt.jar ★核心类库java.lang / java.util 全在这里 │ ├── resources.jar │ └── ext/ 扩展类库 │ ├── lib/ ← 开发工具用到的类库 │ └── tools.jar │ ├── include/ ← JNI 头文件C/C 用 └── src.zip ← ★JDK 类库源码强烈建议解压看重点jre/lib/rt.jar这个文件后面会反复出现。它是Runtime 的缩写Java 核心类库全打包在这个 jar 里。我们要实现的 JVM启动时会把 rt.jar 加入类路径这样才能加载 java.lang.Object、String、System 这些基础类。src.zip也强烈建议解压出来看一眼——里面是 JDK 类库的完整 Java 源码。想知道String.intern()怎么实现的直接看源码比翻文档快多了。七、本系列的两个主力工具javac 和 javap整个系列我们会反复用到这两个工具先把它们摸熟。javac编译器# 基本用法javac HelloWorld.java# 常用参数javac-d./out HelloWorld.java# 指定 class 输出目录javac-encodingUTF-8 Xxx.java# 指定源码编码Windows 中文环境必备javac-gXxx.java# 生成完整调试信息含局部变量表javac-source8-target8Xxx.java# 指定生成 JDK 8 的字节码踩坑提醒Windows 下如果源码有中文注释javac默认按系统编码GBK解析会报编码 GBK 的不可映射字符。养成习惯加上-encoding UTF-8。javap反汇编器本系列出场率最高javap能把 class 文件反汇编成人类可读的字节码。它是我们验证 jvmgo 解析结果是否正确的基准工具——我们自己解析出来的常量池、字节码跟 javap 的输出对照一下就知道对不对。# 常用组合javap-cHelloWorld# 反汇编输出字节码最常用javap-vHelloWorld# 输出详细信息常量池、栈深度、局部变量表javap-pHelloWorld# 显示私有成员javap-c-p-vHelloWorld# 全都来一份推荐看个实际输出这是前面 HelloWorld 的 main 方法public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack3, locals4, args_size1 0: iconst_1 1: istore_1 2: iconst_2 3: istore_2 4: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 7: new #3 // class java/lang/StringBuilder 10: dup 11: invokespecial #4 // Method java/lang/StringBuilder.init:()V 14: ldc #5 // String a b 16: invokevirtual #6 // Method java/lang/StringBuilder.append:(...)... 19: iload_1 20: iload_2 21: iadd 22: invokevirtual #7 // Method java/lang/StringBuilder.append:(I)... 25: invokevirtual #8 // Method java/lang/StringBuilder.toString:()... 28: invokevirtual #9 // Method java/io/PrintStream.println:(...)V 31: return几个关键字段先认个脸熟后面解析 class 文件时都会一一对应上字段含义descriptor方法描述符([Ljava/lang/String;)V 参数是 String[]返回值 voidflags访问标志ACC_PUBLICACC_STATICstack3操作数栈最大深度 3对应 Code 属性的 max_stacklocals4局部变量表大小 4对应 max_localsargs_size1参数个数 1那个 String[] args0: iconst_1偏移量 0 处的指令是 iconst_1#2常量池索引 2重点看到stack3, locals4了吗这两个数字后面实现栈帧时会直接用到——JVM 在创建栈帧时就是按 max_stack 和 max_locals 来分配操作数栈和局部变量表的空间。注意字节码里出现了StringBuilder——字符串拼接a b (a b)被 javac 自动优化成了 StringBuilder 的 append 链。这也是一个值得记住的小知识点Java 的字符串 拼接编译后是 StringBuilder.append()。本篇小结环境准备是动手前的第一步我们完成了三件事理清概念JDK JRE 开发工具JRE JVM 核心类库JVM 是我们要自己实现的核心选定版本JDK 8class 主版本号 52对应 JVM Spec 第 8 版避开模块化等复杂特性装好工具JDK 安装 JAVA_HOME/PATH 配置 三个验证测试同时认识了 JDK 目录结构中最关键的两个东西jre/lib/rt.jar核心类库我们的 JVM 要加载它和bin/javap反汇编器我们对照验证解析结果的基准工具。下一篇轮到 Go 语言了。我们会聊聊为什么用 Go 而不是 C/Java/Rust 来写 JVM并完成 Go 环境的安装。装好之后就可以开始写 jvmgo 的第一行代码了。上一篇【第02篇】Java 虚拟机的工作原理——从 java HelloWorld 说起下一篇【第04篇】Go 语言环境准备——用 Go 写 JVM 的 5 大理由