本系列文章主要根據(jù)張秀宏老師的—— 《自己動手寫 java 虛擬機》一書所做的筆記末盔。該書實現(xiàn)了大部分 JVM 的功能功偿,包括class文件解析盆佣、類加載、指令集械荷、解釋器共耍、方法調(diào)用、數(shù)組和字符串的處理吨瞎、異常處理等痹兜。這本書可以說是了解 Java 虛擬機最淺顯易懂的書籍,隨書提供了每個章節(jié)的代碼关拒,可以完美運行佃蚜。但是這本書所附帶的代碼是用 go 語言書寫的庸娱,如果想要看懂這本書的代碼着绊,首先要學會 go 語言的基本語法。這邊博客記錄了在閱讀這本書之前的準備工作熟尉。我在練習中是根據(jù)張老師的代碼用 Java 語言又實現(xiàn)了一遍归露。雖然用 Java 語言在實現(xiàn)一個自己的虛擬機,然后跑在自帶的虛擬機上有點滑稽斤儿,但是重點還是了解虛擬機內(nèi)部的基本原理剧包。
貼出張秀宏老師這本書的代碼
如果你想跟著張秀宏老師的代碼學習 JVM,那么下面的內(nèi)容正是為你準備的往果。而如果你不想花時間學習另一門語言疆液,那么可以參考我用 Java 實現(xiàn)的相同功能的代碼,下面關(guān)于 go 的準備工作陕贮,可以忽略堕油。
安裝 Go 開發(fā)環(huán)境
下載
Go 語言中文網(wǎng),這個下載速度快一點,下載最新版本之后一路next
即可。
安裝后肮之,其實安裝腳本已經(jīng)自動幫我們把 Go 的安裝路徑添加到系統(tǒng)的環(huán)境變量中了掉缺, 查看系統(tǒng)環(huán)境變量,發(fā)現(xiàn)多了一個 GOROOT
,其值為:C:\go
(默認的安裝路徑)并且在path
中,也自動把 GOROOT\bin
添加了進來,因此直接打開命令行窗口,輸入go version
命令,就會輸出安裝的go
的版本信息.
配置環(huán)境
既然上一步安裝Go
時已經(jīng)添加了環(huán)境變量了,那么接下來還要配置什么呢?
Go
希望我們把所有的代碼都存在在同一個目錄下,這就好比我們用eclipse
時需要一個workspace
,但是eclipse
中的workspace
可以有多個,而Go
希望在一臺主機上只有一個workspace
,所以我們在這里要指定一個,例如D:\workspace
,這個位置隨意,文件夾名字隨意,然后和Go
進行關(guān)聯(lián),這里的關(guān)聯(lián)方法就是在環(huán)境變量中,添加一個GOPATH
,其值為剛剛設(shè)定的D:\workspace
戈擒,其指明了除GOROOT
之外的包含 Go 項目源代碼和二進制文件的目錄眶明。這和Java
中的 classpath 有些類似。當然你可以為GOPATH
指定多個路徑作為 workspace 的筐高,但是這里還是建議一個就好搜囱。
接下來,在D:\workspace
中添加一個目錄src
,這一步是必須的,所有的Go
源代碼都必須放在這個src
目錄下,那有人問,如果我有多個項目,所有源代碼都放在這個目錄下,怎么分清哪個源代碼歸屬于哪個項目呢? 方法很簡單,就是在src
目錄下再用不同的文件夾作為分隔,一個項目一個文件夾,自然就分開了;
Go 語言的包結(jié)構(gòu)
Go
語言以包為單位組織源代碼丑瞧,包可以嵌套,形成層次關(guān)系蜀肘。書中編寫的Go
源文件全部放在jvmgo
包中嗦篱,其中每一章的源文件又分別放在自己的子包中。包層次目錄結(jié)構(gòu)有一個簡單的對應關(guān)系幌缝,比如灸促,第 1 章的代碼在jvmgo\ch01
目錄下。除第 1 章以外涵卵,每一章都是先復制前一章代碼浴栽,然后進行修改和完善。每一章的代碼都是獨立的轿偎,可以單獨編譯為一個可執(zhí)行文件典鸡。
所形成的目錄結(jié)構(gòu)是:
- D:\workspace
- src
- jvmgo
- ch01
- ch02
...
之前也說過在 src 下,通過文件夾將不同的項目分割開坏晦。這里的例子是使用了個二級文件夾來區(qū)分的萝玷,每個項目都是放在src/jvmgo/chXXX
下的,我們平時用一級目錄當然也是可以的昆婿。那么其目錄看起來應該是這樣的:
- D:\workspace
- src
- ch01
- ch02
...
在每一個章節(jié)的 chX(go 的工程)中,都必須有一個main
的包,這個包中的某個文件必須要包含一個main
方法,因為這里每個文件夾被看做一個獨立的工程球碉。這個也很好理解,因為Java
工程中,每個Java
都可以有一個public static void main(String[] args)
方法,這一點不足為奇,但是要注意的是,一旦你選定某個文件夾為一個項目(eg:jvmgo/ch05),并且使用go install jvmgo\ch05
來生成一個 exe 文件, 那么這個目錄下只能有一個main
方法,不能有多個,否則就報錯,類似于
D:\workspace\src\jvmgo\ch05\main2.go:3: main redeclared in this block
previous declaration at E:\workspace\src\jvmgo\ch05\main.go:8
這是因為我在main.go
中聲明了一個main
方法,又在main2.go
中聲明了一個main
方法,所以就報了重復定義的錯誤。
還有一點要說明的是:可以直接在命令行輸入go install your/dir/name
系統(tǒng)會自動從GOROOT/src
和GOPATH/src
兩個文件中找your/dir/name
,而不用非進入到GOPATH/src
路徑下執(zhí)行,而且如果your/dir/name
在上述兩個路徑中均存在,那么前者優(yōu)先級更高仓蛆。
前面是使用go install
命令生成可執(zhí)行文件,但是如果你使用 go run xxx.go
,那么不用保證 xxx.go 所在的包中main
方法是唯一的,main
包中其它文件也可以包含main
方法,因為你運行的是單個文件,不過這只是自己平時練習時用,真正的項目都不會只有單個文件那么簡單睁冬。
關(guān)于 Go 語言的包結(jié)構(gòu),下面兩篇文章是不錯的參考看疙。
GOROOT豆拨、GOPATH 和 project 目錄說明
快速入門 Go 語言
因為本項目是想用Java
實現(xiàn)書中的JVM
項目,而書中所提供的是Go
的源碼,所以這里只要求能看懂Go
的代碼就可以,不必深入細節(jié),相信看到這篇文章,想學習JVM
,那么你的Java
語言一定已經(jīng)很熟練了,那么再快速學習一門語言是很快的,這里推薦Go 的官方教程,一天就能看完,一定要有耐心。
上面提供的網(wǎng)站很不穩(wěn)定,如果人品好的話,打開之后,趕緊一口氣學完;運氣差點肯定就打不開,沒關(guān)系,這里有離線的版本,效果是一樣的;
如果你已經(jīng)安裝好了Go
的開發(fā)環(huán)境,那么直接打開命令行,輸入go tool tour
,瀏覽器就會自動打開一個頁面,和在線版是一樣,而且速度很快,你可以在右側(cè)的代碼區(qū)編寫自己的代碼并運行,這里代碼的改動都是保存在本地的能庆。
go 常用命令
- go build:編譯出可執(zhí)行文件施禾,默認位置是和 main 方法所在的文件同目錄下。
- go run
- go install:先執(zhí)行
go build
命令搁胆,然后把最終編譯成的可執(zhí)行文件放到 GOPATH/bin 目錄下弥搞。 - go get:從指定源上面下載或者更新指定的代碼和依賴(git clone),并對他們進行編譯和安裝(go install)丰涉。
依然用上面的例子來說明拓巧,例如我們下得到第 1 章(每一章都是一個單獨的項目,相互之前沒有關(guān)系)的一個可執(zhí)行程序一死。
go build
構(gòu)建編譯由導入路徑命名的包肛度,以及它們的依賴關(guān)系,但它不會安裝結(jié)果
使用方法:
go build [-o 輸出名] [-i] [編譯標記] [包名]
其具體參數(shù)可以參考下面給出的參考鏈接投慈,這里簡單提一下 go build 的使用:
- 當編譯單個 main 包(文件)承耿,則生成可執(zhí)行文件冠骄。
- 當編譯單個或多個包非主包時(不包含 main 方法),只構(gòu)建編譯包加袋,但丟棄生成的對象(.a)凛辣,僅用作檢查包可以構(gòu)建。
- 當編譯包時职烧,會自動忽略'_test.go'的測試文件扁誓。
在命令行進入到GOPATH/src/jvmgo/ch01
路徑,執(zhí)行:go build main.go
蚀之,這樣就會產(chǎn)生一個 main.exe 的可執(zhí)行文件(默認情況下這個文件的名字為源文件名字去掉.go 后綴)蝗敢,其與 main.go 在同一目錄下。但是如果你真的這樣做是無法生成 main.exe 可執(zhí)行文件的足删,因為 build 命令構(gòu)建單個文件時寿谴,雖然滿足上面的條件 1,但是該文件中還使用了其它的文件中的方法失受。我們平時自己寫一個簡單的打印 helloword 的程序讶泰,所有代碼都在一個文件中,用該命令是可以得到一個可執(zhí)行文件的拂到,但是如果我們的文件中依賴了其它文件中的方法痪署,那么這時候就會報錯,找不到 xxx 方法谆焊。所以這個命令平時使用的不是很多惠桃,用的最多的還是 install 命令浦夷。
參考:go build
go run
和 go build 類似辖试,用來運行單個包含 main 方法的文件,其不生成 exe 文件劈狐,而是直接在命令行中運行該 main 文件罐孝。該命令常用來測試一些功能。
go install
在命令行任意路徑下執(zhí)行:go install jvmgo/ch01
肥缔,這樣就會在GOPATH/bin
路徑下產(chǎn)生一個 ch01.exe(install 命令以 main 方法所在的上一級目錄名作為可執(zhí)行程序名)的可執(zhí)行文件莲兢。之前我們也提到了,必須在 GOPATH 路徑下手動創(chuàng)建一個 src 的文件夾续膳,并將我們寫的所有的 go 文件都剛在該路徑下改艇,但執(zhí)行了上面的 install 命令后,發(fā)現(xiàn)在 GOPATH 下除了 src坟岔,多出了兩個文件夾谒兄。下面解釋下這三個文件夾的作用。
- src 包含項目的源代碼文件社付;
- pkg 包含編譯后生成的包/庫文件承疲;
- bin 包含編譯后生成的可執(zhí)行文件邻耕。
這里特別要提一下的是 pkg 文件夾,其所放的內(nèi)容是聲明燕鸽?我們知道大型項目中都不可能將所有代碼都寫在一個文件中的兄世,而是要分包。那么各個包在 go install 時啊研,會被編譯成 xxx.a御滩,類似 C 中的鏈接庫。
但是 go install 也是有坑的党远,詳見:go install 的工作方式
go 中的文件夾名和包名
在 Java 中艾恼,類的包名和文件路徑必須是對應的,而在 go 中則沒有這種硬性規(guī)定麸锉,但是為了規(guī)范代碼钠绍,Google 官方還是強烈建議我們寫 go 代碼時,文件夾名和包名保持一致花沉。如果不一致柳爽,例如我們有一個文件夾,名為 A碱屁,其下有個 go 文件磷脯,自定義的包名是 B(每個子目錄中只能存在一個 package,否則編譯時會報錯)娩脾,那么在其它文件中使用該文件中的方法時赵誓,需要import A
,而在代碼中使用其方法時柿赊,則使用B.func()
俩功。所以這還是強烈建議,包名和所在文件夾名保持一致碰声。
這里有一個特殊的包诡蜓,叫做main
,在執(zhí)行 go install 命令時胰挑,如何找到整個程序的入口呢蔓罚?光有 main 方法是不行的,包含 main 方法的 go 文件瞻颂,其包名必須也是main
豺谈,同時所有包名為main
的 go 文件中,只能有一個 main 方法贡这。只有滿足以上所有要求茬末,才會得到可執(zhí)行文件,如果 main 方法不在main
下藕坯,執(zhí)行 go install 团南,go 會將所有包都視為 library噪沙,在 pkg 下生成對應的 xxx.a 文件,而不會有可執(zhí)行文件吐根。
關(guān)于開發(fā)工具
這里推薦使用 Intellij idea + 插件
的方式,這里主要是為了看代碼方便,可以很方便的實現(xiàn)方法之間的跳轉(zhuǎn),
go 插件安裝
打開idea
的setting->plugin
,搜索go
,點擊下方的Browse repositorise
,找到Go language(golang.org)support plugin
,官網(wǎng)地址,安裝后重啟編譯器插件才可以用正歼。
其實用 idea+插件的方式,查看 go 的代碼已經(jīng)很舒服了拷橘,不用額外再配置其他 ide局义,畢竟我們的主要精力還是能看懂 go 的代碼,并不寫任何 go 代碼冗疮。
注意:安裝 go 插件萄唇,需要 idea 的版本為2016.2,我在開發(fā)中使用的 idea 為 COMMUNITY 2016.2.5术幔,最新版本的 idea 無法安裝 go 插件另萤!
bytecode 插件安裝
既然虛擬機是和 class 文件打交道的,就免不了查看字節(jié)碼的內(nèi)容诅挑,這里提供一個好用的查看字節(jié)碼的插件——jclasslib Bytecode viewer
四敞,安裝方式同 go 插件的安裝。
使用方法:首先要單獨編譯當前窗口的 java 文件拔妥,使其生成 class 文件忿危,然后在編譯器的View
中選擇show Bytecode with jclasslib
。
用 idea 看源碼
為了配合idea
閱讀《自己動手寫 java 虛擬機》的源碼,這里最簡單的做法:
- 新建一個 go 項目,命名為
jvmgo
,路徑為GOPATH/src
- 需要看哪一章的源碼,就把作者對應的
jvmgo/
文件夾下的ch01
(以第一章為例),復制到上一步新建的項目路徑下,最終結(jié)果應該是:GOPATH/src/jvmgo/ch01