# go to implement java virtual machine 02

Keywords: Go Java JDK Lambda

In the previous article, the command-line parameters are parsed through the flag package. In fact, the input parameters are saved in a structure. In the previous article, for example, the command java -classpath hello.jar HelloWorld, how can the HelloWorld class be found? Is it directly found in hello.jar?

Remember java's class loading mechanism? There is a mechanism called parent delegation. For example, why is it useless to define a String class ourselves? Although it can be passed at compile time, an error will be reported at run time. As shown below, why do I prompt that there is no main method in String class? I wrote it clearly! In fact, when the class is loaded, the String class will first be handed over to the startup classloader to load, that is, to find it in the jre/lib directory in the jdk; if not, use the extended classloader to load, that is, to load it in the jre/lib/ext directory in the jdk, and finally under our user's classpath. The default is the current path, or you can specify the user's classpath through the - classpath command;

The String class is obviously in the rt.jar package under the startup class path, so the official String class is loaded, of course, there is no main method!

Go back to the first questions, such as java -classpath hello.jar HelloWorld: where to find HelloWorld? Now it's very clear. Now it's found in jre/lib under jdk. If it can't be found, it's found in jre/lib/ext. If it can't be found, it's found in the path specified by - classpath. Next, it's implemented with go code. The file directory is as follows. This time, the directory is ch02. Based on the previous ch01 implementation, classpath is a Directory, cmd.go and main.go are files

I. add jre path on command line

In order to better specify the path of jre, we add a parameter - Xjre in the command line, such as ch2 -Xjre "D:\java\jdk8" java.lang.String. If the command line does not specify the - Xjre parameter, then go to your computer environment variable to get JAVA_HOME, which is not much to say, so we need to make a change to the structure here in cmd.go, and add a corresponding resolution, not much to say;

2. Define classpath interface

Define the Entry interface in the classpath directory, which is the Entry to find the specified class file. This interface is very important. According to the path actually uploaded after - classpath, you can determine which instance should be obtained to read the class bytecode file in this path. There are four types of structures: CompositeEntry, WildcardEntry, ZipEntry and DirEntry, All of these four structures need to implement the Entry interface. Let's not worry about how they are implemented first. If they have been implemented, we can use them directly;

package classpath

import (
"os"
"strings"
)

//Here is the separator of the classpath, here is the semicolon, because-classpath Multiple directory names can be specified after the command line, separated by semicolons
const pathListSeparator = string(os.PathListSeparator)

type Entry interface {
//This interface is used to find and load class file
//className Is the relative path of the class, separated by slashes.class End of file, such as to read java.lang.Object，Should be introduced java/lang/Object.class
//The returned data has the class Byte array of files

//Amount to java Medium toString Method
String() string
}

//Create different types of Entry
func newEntry(path string) Entry {
//If more than one class is passed in as a semicolon, instantiate it CompositeEntry this Entry
//for example java -classpath path\to\classes;lib\a.jar;lib\b.jar;lib\c.zip ...This path form
if strings.Contains(path, pathListSeparator) {
return newCompositeEntry(path)
}

//Class full path passed in path The string is*End of number
//for example java -classpath lib\*...
if strings.HasSuffix(path, "*") {
return newWildcardEntry(path)
}

//The full pathname of the class passed in is jar，JAR，zip，ZIP a null-terminated string
//for example java -classpath hello.jar ...  perhaps   java -classpath hello.zip ...
if strings.HasSuffix(path, ".jar") || strings.HasSuffix(path, ".JAR") ||
strings.HasSuffix(path, ".zip") || strings.HasSuffix(path, ".ZIP") {
return newZipEntry(path)
}

//This is what we should do java The file is in the current directory
return newDirEntry(path)
}

3, Realize the mechanism of parent entrustment

The above is the specific structure defined to analyze different paths. Next, we can implement the parent delegation mechanism. In fact, it is relatively easy. The approximate logic is: for example, the command line input is. \ ch02.exe -Xjre "D:\java\jdk8\jre" java.lang.Object, first of all, we will judge whether the jre directory "D:\java\jdk8\jre" provided by us exists. If it does not exist, we will get the jre of the environment variable. Anyway, we will get the jre path;

Then get the Lib / * and lib/ext / * under jre, instantiate two Entry instances respectively (in fact, each Entry instance is to read the io stream of the files in each different path), which correspond to the startup class path and the extension class path respectively; finally, judge whether the - classpath parameter is provided, if not, it defaults to all the current directory File for this user class path

package classpath

import (
"os"
"path/filepath"
)

//Three kinds of path corresponding Entry
type Classpath struct {
//Start class path
bootClasspath Entry
//Extension class path
extClasspath Entry
//User defined classpath
userClasspath Entry
}

//jreOption This parameter is used to read the startup class and extension class
//cpOption This parameter is used to resolve user classes
//Command line input      .\ch02.exe -Xjre "D:\java\jdk8\jre" java.lang.Object
func Parse(jreOption string, cpOption string) *Classpath {
cp := &Classpath{}
//Parsing start class path and extension class path
cp.parseBootAndClasspath(jreOption)
cp.parseUserClasspath(cpOption)
return cp
}

//Splice the paths of the startup class and extension class, and instantiate the corresponding Entry
func (this *Classpath) parseBootAndClasspath(jreOption string) {
//This is the judgment jre If the path exists, it is obtained in the environment variable if it does not exist JAVA_HOMR variable+jre
//All in all, I try my best to get it jdk Lower jre Folder full path
jreDir := getJreDir(jreOption)

//Because we found jdk Lower jre Folder, then the next step is to find the directory where the startup class and extension class are located
//Splicing path: jre/lib/*
jreLibPath := filepath.Join(jreDir, "lib", "*")
this.bootClasspath = newWildcardEntry(jreLibPath)

//Splicing path: jre/lib/ext/*
jreExtPath := filepath.Join(jreDir, "lib", "ext", "*")
this.extClasspath = newWildcardEntry(jreExtPath)
}

//This function is to get the correct jre Folder, note jreOption It's an absolute path
func getJreDir(jreOption string) string {
//If the file path passed in exists, return
if jreOption != "" && exists(jreOption) {
return jreOption
}
//If the path passed in does not exist, then judge whether there is a path under the current path jre Folder
if exists("./jre") {
return "./jre"
}
//The current path does not exist, nor does it exist under the current path jre Folder, then get it directly jdk Lower jre Full path
if jh := os.Getenv("JAVA_HOME"); jh != "" {
return filepath.Join(jh, "jre")
}
//If it doesn't exist, it will be thrown out without this folder
panic("can not find jre folder ")

}

//Determine whether a directory exists, and return if it exists true，Return if it doesn't exist false
func exists(jreOption string) bool {
if _, err := os.Stat(jreOption); err != nil {
if os.IsNotExist(err) {
return false
}
}
return true
}

//Load the user class if-classpath If the parameter of is empty, the current path is the path of the user class by default
func (this *Classpath) parseUserClasspath(cpOption string) {
if cpOption == "" {
cpOption = "."
}
this.userClasspath = newEntry(cpOption)
}

//This method can see that the parent delegation mechanism is implemented
//stay jdk Traverse startup class, extension class and user-defined class in,this ReadClass Is a public method that can be called in other packages
func (this *Classpath) ReadClass(className string) ([]byte, Entry, error) {
className = className + ".class"
if data, entry, err := this.bootClasspath.readClass(className); err == nil {
return data, entry, err
}
if data, entry, err := this.extClasspath.readClass(className); err == nil {
return data, entry, err
}
}

func (this *Classpath) String() string {
return this.userClasspath.String()
}

4, Modify the main.go file

The startJVM function just printed a line of data. Now we can call the Parse method above. According to the jre and class passed in from the command line, we can find the specified class in jre (note that if the jre path specified here does not exist, we will get the jre in the environment variable) according to the parent delegation mechanism, load the class bytecode file of the class into memory, and then give the Print out;

package main

import (
"firstGoPrj0114/jvmgo/ch02/classpath"
"fmt"
"strings"
)

//Command line input      .\ch02.exe -Xjre "D:\java\jdk8\jre" java.lang.Object

func main() {
cmd := parseCmd()
if cmd.versionFlag {
fmt.Println("version 1.0.0")
} else if cmd.helpFlag || cmd.class == "" {
printUsage()
} else {
startJVM(cmd)
}

}

//This function is mainly modified
func startJVM(cmd *Cmd) {
//afferent jdk Medium jre Full path and class name will go inside lib To find or lib/ext Find the corresponding class in
//Command line input      .\ch02.exe -Xjre "D:\java\jdk8\jre" java.lang.Object
cp := classpath.Parse(cmd.XjreOption, cmd.cpOption)
fmt.Printf("classpath:%v class:%v args:%v\n", cp, cmd.class, cmd.args)
//In the full class name.Turn to/,Read in the form of directory class Files, such as the above java.lang.Object It becomes java/lang/Object
className := strings.Replace(cmd.class, ".", "/", -1)
//When reading the specified class, there will be a sequence. First, start the required class and try to load it, then load it in the extended class directory, and finally load it in the user-defined directory
//There are many ways to define a user-defined directory. You can specify yes.zip mode,It can also be.jar mode
if err != nil {
fmt.Printf("Could not find or load mian class %s\n", cmd.class)
return
}
fmt.Printf("class data:%v\n", classData)

}

5. Implementation class of entry interface

Why is this last? Because I don't think it's the core. I've made clear the basic logic in front of me, and then I'll search and read several files with different paths;

As mentioned earlier, there are many parameters after the - classpath we passed in, such as:

//Corresponding DirEntry
java -classpath path\to\service HelloWorld
//Corresponding WildcardEntry
java -classpath path\to\* HelloWorld
//Corresponding ZipEntry
java -classpath path\to\lib2.zip HelloWorldjava -classpath path\to\hello.jar HelloWorld
//Since there can be multiple paths, the corresponding CompositeEntry
java -classpath path\to\classes\*;lib\a.jar;lib\b.jar;lib\c.zip HelloWorld

5.1 DirEntry

This is the easiest. There is only an absolute path in the structure

package classpath

import (
"io/ioutil"
"path/filepath"
)

//A structure is equivalent to a class, newDirEntry Equivalent to the construction method, the following readClass and String Is the way to implement the interface
type DirEntry struct {
//This is used to store the absolute path
absString string
}

func newDirEntry(path string) *DirEntry {
//Convert parameter to absolute path,If it is used on the command line, it will be very accurate to the current file's parent file+Current file
//If it is used in the editor, only the current project path will be reached+File path
absDir, err := filepath.Abs(path)
if err != nil {
panic(err) //Terminate program operation
}
return &DirEntry{absString: absDir}

}

//DirEntry Realized Entry Of readClass Methods, splicing class The absolute path to the bytecode file, and then use the ioUtil Provided in the package ReadFile Function to read
func (self *DirEntry) readClass(className string) ([]byte, Entry, error) {
fileName := filepath.Join(self.absString, className)
return data, self, err
}

//It has also been realized. Entry Of String Method
func (self *DirEntry) String() string {
return self.absString
}

5.2 ZipEntry

This is easier, because there can be multiple files in the zip package, so it's just to traverse and compare the file names

package classpath

import (
"archive/zip"
"errors"
"io/ioutil"
"path/filepath"
)

//There's also an absolute path
type ZipEntry struct {
absPath string
}

//Constructor
func newZipEntry(path string) *ZipEntry {
abs, err := filepath.Abs(path)
if err != nil {
panic(err)
}
return &ZipEntry{absPath: abs}
}

//from zip Analysis in package class File, key here
func (self *ZipEntry) readClass(className string) ([]byte, Entry, error) {
//go There is a secondary school zip Packet read zip Files of type
if err != nil {
return nil, nil, err
}
//This keyword is followed by the current readClass Method will execute after execution
//ergodic zip Is the file name in the package the same as that provided on the command line
for _, f := range reader.File {
if f.Name == className {
rc, err := f.Open()
if err != nil {
return nil, nil, err
}
//defer Key is used to close an open file
defer rc.Close()
if err != nil {
return nil, nil, err
}
return data, self, nil
}
}

}

//Implementation of interface String Method
func (self *ZipEntry) String() string {
return self.absPath
}

5.3 CompositeEntry

Note that this is the case of multiple paths!

package classpath

import (
"errors"
"strings"
)

//Notice, this is a[]Entry Type Oh, because of this Entry There are multiple paths in the corresponding command line
//Multiple paths are separated by semicolons, so we use semicolons to divide them into multiple paths, each of which can be instantiated Entry
//Let's instantiate Entry It's all stored in this slice
type CompositeEntry []Entry

func newCompositeEntry(pathList string) CompositeEntry {
compositeEntry := []Entry{}
for _, path := range strings.Split(pathList, pathListSeparator) {
entry := newEntry(path)
compositeEntry = append(compositeEntry, entry)
}
return compositeEntry

}
//Because there are multiple Entry，Let's go through it and call each one Entry Of readClass Method
func (self CompositeEntry) readClass(className string) ([]byte, Entry, error) {
for _, entry := range self {
if err == nil {
return data, from, nil
}
}

}
func (self CompositeEntry) String() string {
strs := make([]string, len(self))
for i, entry := range self {
strs[i] = entry.String()
}
return strings.Join(strs, pathListSeparator)
}

5.4 WildcardEntry

This corresponds to the path with the wildcard *, which is also a CompositeEntry;

package classpath

import (
"os"
"path/filepath"
"strings"
)

func newWildcardEntry(path string) CompositeEntry {
//Remove the last*
baseDir := path[:len(path)-1] // remove *
//Actually this kind of Entry Namely CompositeEntry
compositeEntry := CompositeEntry{}
//For a function, the following is to pass the function as a parameter. This usage is not very familiar, but I feel that it is similar to jdk8 Zhong Chuan Lambda
//It's the same as a parameter, right
walkFn := func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
//Skip subdirectories with wildcards*In the subdirectory under jar The package is not searchable
if info.IsDir() && path != baseDir {
return filepath.SkipDir
}
//If it is jar Package file, instantiate ZipEntry，Then add to compositeEntry Go inside
if strings.HasSuffix(path, ".jar") || strings.HasSuffix(path, ".JAR") {
jarEntry := newZipEntry(path)
compositeEntry = append(compositeEntry, jarEntry)
}
return nil
}
//This function is traversal baseDir All files in
filepath.Walk(baseDir, walkFn)
return compositeEntry
}

Six. Test

In fact, it's almost the same. According to the command entered in the command line, use the parent delegation mechanism to find the specified class in the specified JRE (or JRE of the environment variable). If not, find it in the current directory of the user. After finding the bytecode file, read the file. The final directory is as follows:

For example, if we want to output the bytecode file of the Object class in jdk8, we first need to go install it according to the way we said in the previous article, and there will be a ch02.exe executable file in the bin directory of the workspace, or we can get the same result without specifying the - Xjre parameter;

You can also test the previously compressed jar package. Note: specify the full path of the jar package!

As for the numbers above, this is the bytecode file. The format of each bytecode file is almost the same, which is composed of magic number, minor version number, major version number, thread pool size, thread pool, etc. It's very easy! Next I'll talk about how to parse this bytecode file...

Posted by jeancharles on Thu, 27 Feb 2020 23:31:24 -0800