手写JVM(一)-命令行

写在前面

我又看完了一本书,庆祝一下。这次是张秀宏大佬的自己动手写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]
java命令用法:java (oracle.com)

 

所有的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源文件一般以.go作为后缀,文件名全部小写,多个单词之间用下划线分隔。
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核心技术系列)

GO语言官网:Standard library – Go Packages

Java虚拟机规范:Chapter 4. The class File Format (oracle.com)

暂无评论

发送评论 编辑评论

|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇