前言
为什么要自己实现 JVM?
- 兴趣驱动:想深入理解 "Write once, run anywhere" 的底层逻辑,跳出 "API 调用程序员" 的舒适区,亲手剖析 JVM 的核心原理。
- 填补空白:目前网上关于 JVM 实现的资料中,针对 Mac 平台的实践较少,希望通过这份笔记给同类需求的开发者提供参考。
为什么选择 Go 语言?
- 开发效率优势:相比 C/C++,Go 语言语法简洁、内存管理更友好,能降低开发门槛,让精力更聚焦于 JVM 核心功能的实现逻辑。
- 学习双赢:借这个项目系统学习 Go 语言,在实践中掌握并发、指针、接口等特性。
参考资料
《自己动手写 Java 虚拟机》—— 张秀宏(核心参考书籍,推荐对 JVM 实现感兴趣的同学阅读)
开发环境
| 工具 / 环境 | 版本 | 说明 |
|---|---|---|
| 操作系统 | MacOS 15.5 | 基于 Intel/Apple Silicon 均可 |
| JDK | 1.8 | 用于字节码分析和测试 |
| Go 语言 | 1.23.10 | 项目开发主语言 |
第一章:实现 JVM 命令行工具
命令行工具是 JVM 的入口,负责解析参数、配置运行环境,本章将从零搭建一个基础的命令行参数解析器。
一、环境准备
1. JDK 1.8 安装与配置
- 从 Oracle 官网 或 AdoptOpenJDK 下载 JDK 1.8 安装包。
配置环境变量(以 Mac 为例):
在
~/.bash_profile或~/.zshrc中添加:export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_xxx.jdk/Contents/Home export PATH=$JAVA_HOME/bin:$PATH执行
source ~/.zshrc生效,通过java -version验证安装成功。
2. Go 1.23.10 安装与配置
- 从 Go 官网 下载对应版本安装包,或通过 Homebrew 安装:
brew install go@1.23。 配置环境变量:
export GOROOT=/usr/local/opt/go@1.23/libexec # 取决于安装路径 export GOPATH=$HOME/go # 自定义工作目录 export PATH=$GOROOT/bin:$GOPATH/bin:$PATH执行
go version验证安装成功。
二、命令行工具核心代码实现
1. 定义命令行参数结构(cmd.go)
该文件负责解析命令行参数,如类路径、主类名、帮助 / 版本信息等。
package main
import (
"flag"
"fmt"
"os"
)
// Cmd 存储命令行参数解析结果
type Cmd struct {
helpFlag bool // 是否显示帮助信息
versionFlag bool // 是否显示版本信息
cpOption string // 类路径(classpath)
class string // 要执行的主类名
args []string // 传递给主类的参数
}
// parseCmd 解析命令行参数并返回 Cmd 实例
func parseCmd() *Cmd {
cmd := &Cmd{}
// 自定义帮助信息打印函数
flag.Usage = printUsage
// 绑定命令行选项到 Cmd 字段
flag.BoolVar(&cmd.helpFlag, "help", false, "print help message")
flag.BoolVar(&cmd.helpFlag, "?", false, "print help message") // 支持 -? 作为帮助选项
flag.BoolVar(&cmd.versionFlag, "version", false, "print version and exit")
flag.BoolVar(&cmd.versionFlag, "v", false, "print version and exit") // 支持 -v 作为版本选项
flag.StringVar(&cmd.cpOption, "cp", "", "classpath") // 类路径选项
// 解析参数
flag.Parse()
// 获取非选项参数(主类名和程序参数)
args := flag.Args()
if len(args) > 0 {
cmd.class = args[0] // 第一个参数为主类名
cmd.args = args[1:] // 后续参数传递给主类
}
return cmd
}
// printUsage 打印命令行使用帮助
func printUsage() {
fmt.Printf("Usage: %s [-options] class [args...]\n", os.Args[0])
}2. 程序主入口(main.go)
主函数根据解析后的参数决定执行逻辑,如打印版本、显示帮助或启动 JVM。
package main
import "fmt"
// main 程序入口函数
func main() {
cmd := parseCmd() // 解析命令行参数
// 根据参数执行对应逻辑
if cmd.versionFlag {
fmt.Println("version 0.0.1") // 版本信息
} else if cmd.helpFlag || cmd.class == "" {
printUsage() // 显示帮助或主类名为空时提示用法
} else {
startJVM(cmd) // 启动 JVM(本章仅打印参数,后续章节实现核心逻辑)
}
}
// startJVM 模拟 JVM 启动(本章仅打印参数)
func startJVM(cmd *Cmd) {
fmt.Printf("classpath: %s\nclass: %s\nargs: %v\n",
cmd.cpOption, cmd.class, cmd.args)
}三、编译与测试
1. 编译代码
在项目目录下执行编译命令,生成可执行文件:
go install ./ch01 # 假设代码放在 ch01 目录下编译成功后,可执行文件会生成在 $GOPATH/bin 目录下(如 ch01)。
2. 测试命令行功能
查看版本:
ch01 -v # 输出:version 0.0.1
查看帮助:
ch01 -help # 输出:Usage: ch01 [-options] class [args...]测试参数解析:
ch01 -cp ./classes com.example.Main arg1 arg2 # 输出: # classpath: ./classes # class: com.example.Main # args: [arg1 arg2]
本章小结
本章完成了 JVM 命令行工具的基础实现,核心功能包括:
- 解析命令行选项(
-help、-version、-cp等); - 提取主类名和程序参数;
- 提供基础的参数校验和帮助提示。
下一章将基于此,实现类路径的查找逻辑和类加载的核心流程,逐步构建完整的 JVM 骨架。
源码地址:https://github.com/Jucunqi/jvmgo.git
评论 (0)