自己动手写java虚拟机 手写jvm虚拟机_赋值

咱们都知道,咱们编译.java并运转.class文件时,需求一些java指令,如最简略的helloworld程序。java初学者可以看一下下面的教程。

这儿的程序最好不要加包名,因为加了包名的话编译和运转需求有所改动。

看这儿的指令。javac为编译指令,咱们知道java的特点是一次编译,处处运转。这儿的编译指的就是javac,关于java程序即.java文件,先要用javac编译成字节码。然后将字节码(.class文件)放到java虚拟机中运转,即上图中的java HelloWorld,java虚拟机把字节码翻译成对应机器上的机器指令,再由机器来履行详细的机器指令。也就是说java程序员是直接与java虚拟机交互,简介与机器交互。所以虚拟机完结的是java指令,也就是咱们要完结的是java这个指令的功用。

那么咱们把榜首个方针定为,完结简略的指令行。即咱们经过指令行能够输入一些内容,虚拟机读取之后能够给必定的反应。

GO言语中有两个和指令行相关的包,分别是os和flag(java中以类库即jar文件导入,go中直接以包的办法导入)。

首先在GOPATH目录下的src里边新建一个jvmgo文件夹作为咱们的作业空间目录,jvmgo里边再新建一个ch01为咱们的榜首个方针源码文件夹,增加cmd.go文件。

在cmd.go里边输入如下代码(因为博客园的增加代码办法不支持go言语上色,所以选用C言语上色,高亮可能不太正确)

package main import "flag" import "fmt" import "os" //界说Cmd结构体 type Cmd struct{ helpFlag bool versionFlag bool cpOption string class string args []string } //解析指令行参数 func parseCmd() *Cmd { cmd:=&Cmd{} //将printUsage函数传给flag.Usage 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解析一切选项,假如Parse失利,则调用flag.Usage打印协助信息 flag.Parse() //调用flag.Args函数捕获未被解析的参数,榜首个参数为主类名,后边的为传递给主类的参数 args:=flag.Args() if len(args)>0{ cmd.class=args[0] cmd.args=args[1:] } return cmd } func printUsage() { fmt.Printf("Usage:%s[-options] class [args...] ",os.Args[0]) }

榜首行为包名,main包,接着引入了三个包os,flag,fmt。os和flag都是处理指令行所需的包,fmt类似于C言语的printf和scanf等格式化IO。再往下界说了一个结构体Cmd,用来这个数据结构来格式化存储输入的指令行信息。helpFlag参数为指令行是否恳求help,versionFlag参数为指令行是否恳求version,cpOption为指令行传入的classpath即方针.class文件地点文件夹,class为指令行传入的.class文件名(不包括.class),args为指令行传入的其他参数。

紧接着是一个parseCmd函数(go言语有函数和办法之分,办法调用需求receiver,函数调用则不需求 ),回来值为*Cmd,用来解析cmd传过来的参数。该函数里边先声明一个cmd并给这个cmd赋值一个新建的Cmd对象。go言语中的“:=”为声明并赋值,而"="为赋值。先把printUsage的函数赋值给flag.Usage,然后调用flag设置需求解析的选项,悉数解析结束,调用Parse函数解析一切选项。解析成功则结束,解析失利则调用printUsage打印到控制台。

flag.Args能够捕获其他没有被解析的参数。上面解析成功之后,榜首个参数就是主类名,剩余的就是传给主类的参数。

东西类编写完结,下一个是

主函数。先上主函数代码:

package main import "fmt" func main() { //调用parseCmd解析指令行参数 cmd:=parseCmd() if cmd.versionFlag{ //输入了-version选项 fmt.Println("version 0.0.1") }else if cmd.helpFlag||cmd.class==""{ //输入了-help选项 printUsage() }else{ //发动jvm stratJVM(cmd) } } func stratJVM(cmd *Cmd){ fmt.Printf("classpath:%s class:%s args:%v ", cmd.cpOption,cmd.class,cmd.args) }

跟java类似,在go里边main是一个特殊的包,go程序的入口就是main函数,可是不接受任何参数,也不能有回来值。main函数先调用parseCmd解析指令行参数,假如是-version则回来版本号,假如是-help则回来协助信息,假如是其他则发动jvm,这儿用一些输出信息“伪装”发动了jvm,真正的jvm代码后边会加上。

至此,对指令行的解析作业悉数完结。先展现一下整个作业目录的结构,不然后边编译运转的时分会犯错。

咱们的作业目录是D盘下的JVM里的goWorkSpace,再下面src,jvmgo,ch01,ch01里边包括的是咱们的go文件。

来测验一下,翻开一指令行,输入go install jvmgoch01。这个指令是运用go.exe来install文件,这个文件存在于GOPATH下面的文件夹(jvmgoch01中),结果如图:

然后在作业空间(GOPATH)的bin文件夹中就多出了一个ch01.exe。

在此处翻开指令行。能够进行一些操作:

到这儿,咱们的指令行东西就完结了,尽管还没有触及真正的虚拟机规划,但这也是虚拟机运转的重要一步,后边会逐渐介绍虚拟机的规划。

同理,如果在unlock操作中,就是释放了锁,然后unpark,这儿就不详细讲了。

咱们知道HashMap不是一个线程安全的容器,最简略的办法使HashMap变成线程安全就是运用Collections.synchronizedMap,它是对HashMap的一个包装

public static Map m=Collections.synchronizedMap(new HashMap());

同理关于List,Set也供给了类似办法。

可是这种办法只适合于并发量比较小的状况。

咱们来看下synchronizedMap的完成

它会将HashMap包装在里面,然后将HashMap的每个操作都加上synchronized。

由于每个办法都是获取同一把锁(mutex),这就意味着,put和remove等操作是互斥的,大大减少了并发量。

下面来看下ConcurrentHashMap是怎么完成的

在 ConcurrentHashMap内部有一个Segment段,它将大的HashMap切分成若干个段(小的HashMap),然后让数据在每一段上Hash,这样多个线程在不同段上的Hash操作一定是线程安全的,所以只需要同步同一个段上的线程就可以了,这样完成了锁的别离,大大增加了并发量。

在运用ConcurrentHashMap.size时会比较费事,由于它要计算每个段的数据和,在这个时分,要把每一个段都加上锁,然后再做数据计算。这个就是把锁别离后的小小坏处,可是size办法应该是不会被高频率调用的办法。

在完成上,不运用synchronized和lock.lock而是尽量运用trylock,一起在HashMap的完成上,也做了一点优化。这儿就不提了。