自己动手写 Java 虚拟机笔记 - 第一部分:从零搭建命令行工具

自己动手写 Java 虚拟机笔记 - 第一部分:从零搭建命令行工具

Luca Ju
2025-06-13 / 0 评论 / 8 阅读 / 正在检测是否收录...

前言

为什么要自己实现 JVM?

  • 兴趣驱动:想深入理解 "Write once, run anywhere" 的底层逻辑,跳出 "API 调用程序员" 的舒适区,亲手剖析 JVM 的核心原理。
  • 填补空白:目前网上关于 JVM 实现的资料中,针对 Mac 平台的实践较少,希望通过这份笔记给同类需求的开发者提供参考。

为什么选择 Go 语言?

  • 开发效率优势:相比 C/C++,Go 语言语法简洁、内存管理更友好,能降低开发门槛,让精力更聚焦于 JVM 核心功能的实现逻辑。
  • 学习双赢:借这个项目系统学习 Go 语言,在实践中掌握并发、指针、接口等特性。

参考资料

《自己动手写 Java 虚拟机》—— 张秀宏(核心参考书籍,推荐对 JVM 实现感兴趣的同学阅读)

开发环境

工具 / 环境版本说明
操作系统MacOS 15.5基于 Intel/Apple Silicon 均可
JDK1.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

    version.png

  • 查看帮助:

    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 命令行工具的基础实现,核心功能包括:

  1. 解析命令行选项(-help-version-cp 等);
  2. 提取主类名和程序参数;
  3. 提供基础的参数校验和帮助提示。

下一章将基于此,实现类路径的查找逻辑和类加载的核心流程,逐步构建完整的 JVM 骨架。

源码地址:https://github.com/Jucunqi/jvmgo.git
0

评论 (0)

取消