写在前面
我又看完了一本书,庆祝一下。这次是张秀宏大佬的自己动手写Java虚拟机 (Java核心技术系列),书中的知识对本人的提升很有帮助,建议有兴趣的人可以去买一本看看,支持正版,这篇博客正是对其的笔记吧,我顺便把我一些对JVM的理解写上去了。JVM的思想,提高效率等等,总之,收获大大。
不同于《深入理解java虚拟机》纯理论,张秀宏大佬的自己动手写Java虚拟机 (Java核心技术系列)全是实践,还挺轻松,吐槽一下,我又想起我啃周志明大佬的《深入理解java虚拟机》的苦日子。
如果,我有时间的话,应该也想使用java语言乃至其他语言,去实现一下,毕竟,我可是整整写了快10万字的笔记和总结,嗯嗯嗯,实现起来,应该不难。
兜兜转转的,至少我也实现了一个java虚拟机。
本文为我看张秀宏大佬的自己动手写Java虚拟机 (Java核心技术系列)的时候做的笔记。
1.准备工作
以下开发环境 基于go version go1.20.3

Oracle的Java虚拟机实现是通过java命令来启动的,主类名由命令行参数指定。java命令有如下4种形式:
java [-options] class [args] java [-options] -jar jarfile [args] javaw [-options] class [args] javaw [-options] -jar jarfile [args]
所有的Go源代码被都放在一个工作空间中。所谓工作空间,实际上就是一个目录结构,这个目录结构包含三个子目录。
- ·src目录中是Go语言源代码。
- ·pkg目录中是编译好的包对象文件。
- ·bin目录中是链接好的可执行文件。
在目录下创建jvmgo目录,在jvmgo目录下创建ch01目录。现在,工作空间的目录结构如下:

2.java命令
Java虚拟机的工作是运行Java应用程序。和其他类型的应用程序一样,Java应用程序需要一个入口,也就是main()方法。Java应用程序通过main()方法启动。
如:
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, world!");
}
}
Java虚拟机规范没有明确规定Java虚拟机要从哪个类启动应用程序。是由虚拟机实现自行决定的。比如Oracle的Java虚拟机实现是通过java命令来启动的,主类名由命令行参数指定。java命令有如下4种形式:
java [-options] class [args] java [-options] -jar jarfile [args] javaw [-options] class [args] javaw [-options] -jar jarfile [args]
- 可以向java命令传递三组参数:选项、主类名(或者JAR文件名)和main()方法参数。选项由减号(–)开头。通常,第一个非选项参数给出主类的完全限定名(fully qualified class name)。
- 但是如果用户提供了–jar选项,则第一个非选项参数表示JAR文件名,java命令必须从这个JAR文件中寻找主类。
- javaw命令和java命令几乎一样,唯一的差别在于,javaw命令不显示命令行窗口,因此特别适合用于启动GUI(图形用户界面)应用程序。
- 选项可以分为两类:标准选项和非标准选项。标准选项比较稳定,不会轻易变动。非标准选项以-X开头,很有可能会在未来的版本中变化。非标准选项中有一部分是高级选项,以-XX开头。
表1-1列出了java命令常用的选项及其用途。

完整的java命令用法请参考
http://docs.oracle.com/javase/8/docs/technotes/tools/windows/java.html
3.编写命令行工具
下面根据java命令的第一种用法,编写一个类似的命令行工具。
先定义一个结构体来表示命令行选项和参数。在ch01目录下创建cmd.go文件 ,然后在其中定义Cmd结构体,代码如下:
package main
import (
"flag"
"fmt"
"os"
)
type Cmd struct {
helpFlag bool
versionFlag bool
cpOption string
class string
args []string
}在Java语言中,API一般以类库的形式提供。在Go语言中,API则是以包(package)的形式提供。包可以向用户提供常量、变量、结构体以及函数等。Java内置了丰富的类库,Go也同样内置了功能强大的包。
前置知识
使用flag获取命令行参数
flag.TypeVar()
基本格式如下:
flag.TypeVar()(Type指针, flag名, 默认值, 帮助信息),TypeVar 可以是 IntVar、StringVar、BoolVar 等,其功能为将 flag 绑定到一个变量上。
- 第一个参数:为一个Type类型的指针(其他类型类似)
- 第二个参数:定义参数名称,命令行可以通过指定 flag名的值来传递数据。
- 第三个参数:定义参数的默认值,如果命令行没有接收到 flag名的值,则使用默认值。
- 第四个参数:定义参数的实用信息,也就是help信息。
flag.Parse()来对命令行参数进行解析。 flag.Args() 返回命令行参数后的其他参数
输入格式为:
.exe [-option] class [args...]
即
.exe -flag名 想输入Type指针的值 [args...]
继续编辑cmd.go文件,在其中定义parseCmd()函数,代码如下:
func parseCmd() *Cmd {
cmd := &Cmd{}
flag.Usage = printUsage
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.StringVar(&cmd.cpOption, "classpath", "", "classpath")
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
}
首先设置flag.Usage变量,把printUsage()函数赋值给它;然后调用flag包提供的各种Var()函数设置需要解析的选项;接着调用Parse()函数解析选项。如果Parse()函数解析失败,它就调用printUsage()函数把命令的用法打印到控制台。
flag.Usage = printUsage //重写Usage来定义Usage信息
args := flag.Args() //参数中没有能够按照预定义的参数解析的部分,通过flag.Args()即可获取,是一个字符串切片
printUsage()函数的代码如下:
func printUsage() {
fmt.Printf("Usage: %s [-option] class [args...]\n", os.Args[0])
}如果解析成功,调用flag.Args()函数可以捕获其他没有被解析的参数。其中第一个参数就是主类名,剩下的是要传递给主类的参数。这样,用了不到40行代码,我们的命令行工具就编写完了。下面来测试它。
Go语言有函数(Function)和方法(Method)之分,方法调用需要receiver,函数调用则不需要。
4.测试代码
在ch01目录下创建main.go文件,然后输入下面的代码。
package main
import "fmt"
func main() {
cmd := parseCmd()
if cmd.versionFlag {
fmt.Println("java version 0.0.1")
} else if cmd.helpFlag || cmd.class == "" {
printUsage()
} else {
startJVM(cmd)
}
}注意,与cmd.go文件一样,main.go文件的包名也是main。在Go语言中,main是一个特殊的包,这个包所在的目录(可以叫作任何名字)会被编译为可执行文件。Go程序的入口也是main()函数,但是不接收任何参数,也不能有返回值。main()函数先调用ParseCommand()函数解析命令行参数,如果一切正常,则调用startJVM()函数启动Java虚拟机。如果解析出现错误,或者用户输入了-help选项,则调用PrintUsage()函数打印出帮助信息。如果用户输入了-version选项,则输出(一个滥竽充数的)版本信息。因为我们还没有真正开始编写Java虚拟机,
所以startJVM()函数暂时只是打印一些信息而已,代码如下:
func startJVM(cmd *Cmd) {
fmt.Printf("classpath:%s class:%s args:%v\n", cmd.cpOption, cmd.class, cmd.args)
}
打开命令行窗口,执行下面的命令编译本章代码。
go install jvmgo\ch01
命令执行完毕后,如果没有看到任何输出就证明编译成功了,此时在工作目录下会出现ch01.exe文件。现在,可以用各种参数进行测试。
输入格式为:
.exe [-option] class [args...]
即
.exe -flag名 想输入Type指针的值 [args...]
测试结果如图所示。

5.参考资料
尚硅谷宋红康:JVM全套教程:https://www.bilibili.com/video/BV1PJ411n7xZ
周志明:深入理解java虚拟机
张秀宏:自己动手写Java虚拟机 (Java核心技术系列)


