MiniJavaVM一个Java虚拟机的设计和实现要点.docx
- 文档编号:27404370
- 上传时间:2023-06-30
- 格式:DOCX
- 页数:69
- 大小:1.67MB
MiniJavaVM一个Java虚拟机的设计和实现要点.docx
《MiniJavaVM一个Java虚拟机的设计和实现要点.docx》由会员分享,可在线阅读,更多相关《MiniJavaVM一个Java虚拟机的设计和实现要点.docx(69页珍藏版)》请在冰豆网上搜索。
MiniJavaVM一个Java虚拟机的设计和实现要点
MiniJavaVM——一个Java虚拟机的设计和实现
摘要
本文叙述了Java虚拟机(JVM)的概念及如何设计和实现一个Java虚拟机——MiniJavaVM。
着重介绍了虚拟机的体系结构及如何设计和实现这个体系结构。
在探讨虚拟机的设计过程中详细介绍了MiniJavaVM虚拟机各部分的设计,包括类的装载和解析,内存管理,执行引擎,方法调用和异常处理部分。
最后通过测试MiniJavaVM来验证设计和实现的正确性。
关键词
Java虚拟机(JVM)字节码类装载执行引擎本地方法
MiniJavaVM–adesignandimplementationofaJavaVirtualMachine
Abstract
ThispaperdescribestheconceptionofJavaVirtualMachine(JVM)andhowtodesignandimplementaJavaVirtualMachine–MiniJavaVM.ItemphasizesthearchitectureofJVMandhowtodesignandimplementthearchitecture.ItdescribesthedetailsabouteachpartofJVMwhendiscussinghowtodesigntheJVM,includingclass-loadingandresolution,memorymanagement,executionengine,methodinvokingandexception-handling.Atlast,thecorrectnessofthedesignandimplementationisvalidatedbytestingMiniJavaVM.
Keywords
JavaVirtualMachine(JVM),bytecode,Class-loading,executionengine,NativeMethod
第一章
绪论
一.1Java及Java虚拟机
说起Java,人们首先想到的是Java编程语言,然而事实上,Java是一种技术,它由四方面组成:
Java编程语言、Java类文件格式、Java虚拟机和Java应用程序接口(JavaAPI)。
它们的关系如下图所示:
[1]
运行期环境代表着Java平台,开发人员编写Java代码(.java文件),然后将之编译成字节码(.class文件)。
最后字节码被装入内存,一旦字节码进入虚拟机,它就会被解释器解释执行,或者是被即时代码发生器有选择的转换成机器码执行。
从上图也可以看出Java平台由Java虚拟机和Java应用程序接口搭建,Java语言则是进入这个平台的通道,用Java语言编写并编译的程序可以运行在这个平台上。
这个平台的结构如下图所示:
[1]
在Java平台的结构中,可以看出,Java虚拟机(JVM)处在核心的位置,是程序与底层操作系统和硬件无关的关键。
它的下方是移植接口,移植接口由两部分组成:
适配器和Java操作系统,其中依赖于平台的部分称为适配器;JVM通过移植接口在具体的平台和操作系统上实现;在JVM的上方是Java的基本类库和扩展类库以及它们的API,利用JavaAPI编写的应用程序(application)和小程序(Javaapplet)可以在任何Java平台上运行而无需考虑底层平台,就是因为有Java虚拟机(JVM)实现了程序与操作系统的分离,从而实现了Java的平台无关性。
[1]
什么是Java虚拟机?
Java虚拟机是运行所有Java程序的抽象计算机,它仅仅是由一个规范来定义的抽象的计算机。
当提及“Java虚拟机”时,可能指的是如下三种不同的东西:
•抽象规范
•一个具体的实现
•一个运行中的虚拟机实例[2]
Java虚拟机负责Java程序设计语言的内存安全、平台无关和安全特性。
Java虚拟机屏蔽了与具体操作系统平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。
Java虚拟机(JVM)在多个平台上实现统一语言。
Java之所以得以大行其道,除了它是一门面向对象、构造精美的语言之外,更重要的原因在于:
它摆脱了具体机器的束缚,使跨越不同平台编写程序成为可能。
一.2Java虚拟机的体系结构
在Java虚拟机规范中,一个虚拟机实例的行为是分别按照子系统、内存区、数据类型以及指令这几个术语来描述的。
这些组成部分一起展示了抽象的虚拟机的内部抽象体系结构。
但是规范中对它们的定义并非是要强制规定Java虚拟机实现内部的体系结构,更多的是为了严格地定义这些实现的内部特征。
规范本身通过定义这些抽象的组成部分以及它们之间的交互,来定义任何Java虚拟机实现都必须遵守的行为。
每个JVM都有两种机制,一个是装载具有合适名称的类(类或是接口),叫做类装载子系统;另外的一个负责执行包含在已装载的类或接口中的指令,叫做运行引擎。
每个JVM又包括方法区、堆、Java栈、程序计数器和本地方法栈这五个部分,这几个部分和类装载机制与运行引擎机制一起组成了Java虚拟机的体系结构。
图1.2.1描述了Java虚拟机的结构框图,包括在规范中描述的主要子系统和内存区。
每个Java虚拟机都有一个类装载器子系统,它根据给定的全限定名来装入类型(类或接口)。
同样,每个Java虚拟机都有一个执行引擎,它负责执行那些包含在被装载类的方法中的指令。
Java虚拟机的运行时数据区存储了许多运行时数据,例如,字节码,从已装载的class文件中得到的其他信息,程序创建的对象,传递给方法的参数,返回值,局部变量,以及运算的中间结果等。
Java虚拟机把这些东西都组织到几个“运行时数据区”中,以便于管理。
某些运行时数据区是由程序中所有线程共享的,还有一些则只能由一个线程拥有。
每个Java虚拟机实例都有一个方法区及一个堆,它们是由该虚拟机实例中所有线程共享的。
当虚拟机装载一个class文件时,它会从这个class文件包含的二进制数据中解析类型信息。
然后把这些类型信息放到方法区中。
当程序运行时,虚拟机会把所有该程序在运行时创建的对象都放到堆中。
图1.2.2描述了这些内存区域。
当每一个线程被创建时,它都将得到它自己的PC寄存器以及一个Java栈。
如果线程正在执行的是一个Java方法(非本地方法),那么PC寄存器的值将总是指示下一条将被执行的指令,而它的Java栈则总是存储该线程中Java方法调用的状态——包括它的局部变量,被调用时传进来的参数,它的返回值,以及运算的中间结果等。
而本地方法调用的状态,则是以某种依赖于具体实现的方式存储在本地方法栈中,也可能是在寄存器或其他某些与特定实现相关的内存区中。
Java栈是由许多栈帧(stackframe)组成的,一个栈帧包含一个Java方法调用的状态。
当线程调用一个Java方法时,虚拟机压入一个新的栈帧到该线程的Java栈中;当该方法返回时,这个栈帧被从Java栈中弹出并抛弃。
Java虚拟机没有寄存器,其指令集使用Java栈来存储中间数据。
这样设计的原因是为了保持Java虚拟机的指令集尽量紧凑,同时也便于Java虚拟机在那些只有很少通用寄存器的平台上实现。
另外,Java虚拟机的这种基于栈的体系结构,也有助于运行时某些虚拟机实现的动态编译器和即时编译器的代码优化。
图1.2.3描绘了Java虚拟机为每个线程创建的内存区,这些内存区是私有的,任何线程都不能访问另一个线程的PC寄存器或者Java栈。
[3]
一.3MiniJavaVM的功能
✧能够装载并解析javaclass文件
对于已经编译好的javaclass文件,能够读取该class文件的内容,装载该类,并保存在程序内部的数据结构中。
当在程序运行的过程中需要解析该类时,进行解析,并替换符号引用为直接引用
✧在完成虚拟机的初始化后,能够找到main函数并执行程序[13]
对于指定的入口类,在虚拟机完成了初始化后,寻找该类的main()方法,如果找到,则执行该方法,否则抛出异常,虚拟机运行中止。
✧支持Java虚拟机规范中规定的200多个操作码的功能
实现了Java虚拟机的200多个操作码的功能,由此使MiniJavaVM这个虚拟机模拟Java虚拟机的功能成为可能,这200多个操作码包括:
Ø栈和局部变量操作指令
✓将常量池入指令
✓从栈中的局部变量中装载值指令
✓将栈中的值存入局部变量指令
✓通用栈操作指令
Ø类型转换指令
Ø整数运算指令
Ø逻辑运算指令
✓移位操作指令
✓按位布尔运算指令
Ø浮点运算指令
Ø对象和数组指令
✓对象操作指令
✓数组操作指令
Ø控制流指令
✓条件分支指令
✓比较指令
✓无条件转移指令
✓表跳转指令
Ø异常指令
Øfinally子句指令
Ø方法调用与返回指令
✓方法调用指令
✓方法返回指令
Ø线程同步指令
✧具有内存管理和垃圾收集机制
Java虚拟机对内存的管理使得java程序具有很高的安全性,程序员不用担心内存访问越界问题,也不用为在合适的时候释放分配的空间而费心。
垃圾收集机制的存在解决何时回收不用的内存和如何回收内存的问题。
✧支持非本地方法调用
按照Java虚拟机规范中的要求来设置非本地方法的调用情况,包括参数压栈,分配局部变量空间,压入方法调用的栈桢等。
✧支持本地方法调用
Java虚拟机中所有与本地方法相关的部分都重新写过,以动态链接库的形式为MiniJavaVM工程提供支持。
MiniJavaVM的本地方法只实现最基本的功能,不再负责虚拟机的安全机制。
✧支持异常处理
有了异常处理,就能够在程序运行时平稳处理意外情况。
根据Javaclass文件中的异常表,MiniJavaVM程序支持所有的异常处理,并在不能解决异常时输出异常信息,虚拟机停止运行。
✧能够运行与I/O无关的完整Java程序,并提供参数供查看运行效果
提供了-version,-showversion,–help,-?
,–verbose命令。
-version命令显示MiniJavaVM的版本信息,然后退出
-showversion命令显示MiniJavaVM的版本信息,然后继续运行Java程序
-help,-?
命令显示帮助信息
-verbose命令输出详细数据显示运行过程
一.4MiniJavaVM的运行环境及开发工具
开发平台:
WindowsXP/2003
开发语言:
ANSIC/C++
开发工具:
VisualStudioC++/VisualS
运行平台:
WindowsXP/2000/2003
第二章
系统设计
二.1唯一的虚拟机MiniJavaVM
一个运行的Java虚拟机实例的天职就是:
负责运行一个Java程序。
当启动一个Java程序时,一个虚拟机实例也就诞生了。
当该程序关闭退出,这个虚拟机实例也就随之消亡。
如果在同一台计算机上同时运行三个Java程序,将得到三个Java虚拟机实例。
每个Java程序实例都运行于它自己的Java虚拟机实例中。
[2]
为此,需要有一种机制保证在运行过程中只有一个Java虚拟机的实例产生。
首先要定义表示Java虚拟机的类,然后保证这个类有只一个实例。
一种可以供选择的方案是使用Singleton设计模式,保证Java虚拟机实例在整个程序运行过程中只有一个;另一种是可以考虑MFC(MicrosoftFundamentalClass)中唯一的全局变量theApp来表示程序运行的实例,通过AfxGetApp()的方法来得到该唯一的全局变量的指针,然后在此基础上进行操作。
在这里,我们选择MFC方式定义唯一的全局变量来表示Java虚拟机的运行实例,可以在程序进入main()函数前完成虚拟机的初始化。
通过定义一个全局函数来返回虚拟机实例的指针。
二.2MiniJavaVM的构成要素
二.2.1虚拟机总体框架
我们的MiniJavaVM的框架合理地组织了虚拟机运行时所需的各模块,将各模块的输入与输出有效地结合在一起,使这些模块组合在一起完成了Java虚拟机的功能。
这些模块包括:
命令参数解析模块、类的装载和解析模块、内存管理模块、执行引擎模块、方法调用模块、异常处理模块、多线程处理模块(未完成)。
我们的MiniJavaVM总的组织方式如图2.2.1所示。
其中除命令参数解析模块外,其他模块一起构成了完整的MiniJavaVM虚拟机,这些模块之间协同合作,完成了虚拟机的功能。
其中命令参数解析模块负责解析命令,根据MiniJavaVM后的参数来设定虚拟机的运行模式及输出信息;类的装载和解析模块能从class文件或是rt.jar文件中装载指定名称的Java类,并采用迟解析的方式在需要时解析该类,类的信息维护在虚拟机的一个数据结构中;内存管理模块负责为类的实例及静态字段分配空间,并在虚拟机内维护类的实例和静态字段,当虚拟机空间不足时会启动垃圾回收机制来回收内存;执行引擎模块负责解释执行200多个操作码,解释的过程包括对栈桢、栈、PC、局部变量区的修改;多线程处理模块负责维护虚拟机内的表示线程的数据结构,在语言级提供多线程支持;方法调用模块负责处理方法调用过程,对于非本地方法,包括找到调用方法的指针,新建栈桢、将方法参数设置在新栈桢的局部变量区,调用方法并将返回值压栈的过程,对于本地方法,包括找到调用方法的指针,将方法参数用汇编的方式压栈,调用本地方法并将返回值压栈的过程;异常处理模块负责处理虚拟机抛出的异常,记录异常产生处的异常信息,并试图通过查找当前方法的异常表来处理异常信息,如果能够通过异常表找到处理异常的代码,则修改PC的值使虚拟机处理当前异常,否则,当虚拟机不能处理该异常时,输出异常信息,然后终止虚拟机的运行。
通过这几个模块的协同合作,我们的MiniJavaVM虚拟机能够很好地模拟Java虚拟机的功能。
二.2.2命令参数解析模块
命令参数解析模块负责解析命令行,根据MiniJavaVM后的参数来设定虚拟机的运行模式及输出信息。
该模块的设计如图2.2.2所示:
命令参数解析模块在解析完命令行参数后,通过得到虚拟机的唯一实例的指针调用设置参数的方法来设置虚拟机运行时的参数。
二.2.3类的装载和解析模块
类的装载和解析模块负责从javaclass文件或是rt.jar文件中装载指定名称的Java类,并采用迟解析的方式在需要时解析该类,类的信息维护在虚拟机的一个数据结构中。
我们的MiniJavaVM类的装载和解析模块设计如图2.2.3所示:
其中要装载的类文件有两种渠道获得,一种是直接查找相应类的class文件,一种是从rt.jar文件中得到类的class文件的数据,为了统一这两种方式,可以先从这两种方法中生成类文件的字节流,再交由下一步程序处理。
当生成class文件的字节流后,通过指定的模块读取字节流中的信息,生成该Java类在虚拟机中对应的数据。
这样,类装载的部分算是完成。
在虚拟机运行的过程中,会需要解析类中的常量池,将符号引用替换为直接引用。
二.2.4内存管理模块
内存管理模块负责为类的实例及静态字段分配空间,并在虚拟机内维护类的实例和静态字段,当虚拟机空间不足时会启动垃圾回收机制来回收内存。
在Java虚拟机中,关于被装载类型的信息存储在一个逻辑上称为方法区的内存中,所有线程共享方法区,因此它们对方法区数据的访问必须被设计为是线程安全的。
方法区的大小不必是固定的,虚拟机可以根据应用的需要动态调整。
同样,方法区也不必是连续的,方法区可以在一个堆(甚至是虚拟机自己的堆)中自由分配。
另外,虚拟机也可以允许用户或者程序员指定方法区的初始大小以及最小和最大尺寸等。
[4]
方法区也可以被垃圾收集。
因为虚拟机允许用户定义的类装载器来动态扩展Java程序,因此一些类也会成为程序“不再引用”的类。
当某个类变为不再被引用的类时,Java虚拟机可以卸载这个类(垃圾收集),从而使方法区占据的内存保持最小。
为了简单,我们的MiniJavaVM的方法区使用虚拟机自己的堆,不参与垃圾回收,同时,类的静态字段及一些特殊的类的实例(如与每个类相关的Class类的实例)也不参与垃圾回收。
我们的MiniJavaVM内存管理模块设计如图2.2.4所示:
当MiniJavaVM虚拟机请求一个可以被回收的内存空间(比如普通类的实例空间)时,则通过内存管理模块分配可回收的内存,所分配的内存的实地址经过映射后返回给虚拟机,同时已分配的内存地址记录在一张哈希表中,供快速查找所用。
如果虚拟机已经没有可分配的空间,则运行垃圾收集,垃圾收集完成后再分配内存。
当虚拟机请求一个不可被回收的内存空间(比如类的静态字段空间,表示已装载类的Class类的实例等)时,通过内存管理模块分配不可回收内存,所分配的内存的实地址经过映射后返回给虚拟机,同时已分配的内存地址记录在一张哈希表中,供快速查找所用。
当垃圾收集完成后仍没有可分配的内存地址可用时,虚拟机退出。
二.2.5执行引擎模块
任何Java虚拟机实现的核心都是它的执行引擎。
在Java虚拟机规范中,执行引擎的行为使用指令集来定义。
对于每条指令,Java虚拟机规范都详细规定了当实现执行到该指令时应该处理什么。
[5]执行引擎模块负责解释执行Java200多个操作码,解释的过程包括对Java栈桢、Java栈、PC、局部变量区的修改。
我们的MiniJavaVM执行引擎模块设计如图2.2.5所示:
在调用执行引擎模块时,会传入一字节码流,执行引擎模块负责解释这一字节码流。
在解释字节码过程中,Java虚拟机可能会装载新的Java类,分配某个Java类的实例,进行数学运算,修改当前java栈桢、Java栈、PC、局部变量区等。
二.2.6方法调用模块
方法调用模块负责处理方法调用过程,对于非本地方法,包括找到调用方法的指针,新建栈桢、将方法参数设置在新栈桢的局部变量区,调用方法并将返回值压栈的过程,对于本地方法,包括找到调用方法的指针,将方法参数用汇编的方式压栈,调用本地方法并将返回值压栈的过程。
我们的MiniJavaVM方法调用模块的设计如图2.2.6所示:
虚拟机为每一个调用的Java(非本地)方法一个新的栈帧。
栈帧包括:
为方法的局部变量所预留的空间,该方法的操作数栈,以及特定虚拟机实现需要的其他所有信息。
局部变量和操作数栈的大小在编译时计算出来,并放置到class文件中去,然后虚拟机就能够了解到方法的栈帧需要多少内存。
当虚拟机调用一个方法的时候,它为该方法创建恰当大小的栈帧,再将新的栈帧压入Java栈。
处理实例方法时,虚拟机从所调用方法栈帧内的操作数栈中弹出objectref和args。
虚拟机把objectref作为全局变量0放到新的栈帧中,把所有的args作为局部变量1,2,……等处理。
Objectref是隐式传给所有实例方法的this指针。
对于类方法,虚拟机只从所调用的方法栈帧中的操作数栈中弹出参数,并将它们放到新的栈帧中去作为局部变量0,1,2……当objectref和args(对于类方法则只有args)被赋给新栈帧中的局部变量后,虚拟机把新的栈帧作为当前栈帧,然后将程序计数器指向新方法的第一条指令。
[6]
虚拟机使用一种“与实现相关”的风格调用本地方法。
当调用本地方法时,虚拟机不会将一个新的栈帧压入Java栈。
当线程进入到本地方法的那一刻,它就将Java栈抛在身后。
直到本地方法返回以后,Java栈才被重新调用。
这里本虚拟机的实现将使用调用动态链接库中的方法来实现本地方法调用的过程。
二.2.7异常处理模块
异常处理模块负责处理虚拟机抛出的异常,记录异常产生处的异常信息,并试图通过查找当前方法的异常表来处理异常信息,如果能够通过异常表找到处理异常的代码,则修改PC的值使虚拟机处理当前异常,否则,当虚拟机不能处理该异常时,输出异常信息,然后终止虚拟机的运行。
我们的MiniJavaVM异常处理模块的设计如图2.2.7所示:
第三章
虚拟机框架的实现
Java虚拟机的体系结构如图3.1所示:
实现虚拟机框架,需要考虑以下几点:
✧虚拟机的数据类型和字长
✧如何实现栈结构
✧如何装载和解析类
✧如何调用本地和非本地方法
✧如何实现执行引擎
✧如何实现多线程
✧机如何组织方法区和堆
✧如何进行垃圾回收
✧虚拟机如何处理异常
为了解决这些问题,这里我们把MiniJavaVM虚拟机的实现总体分为三部分。
第一部分为主体部分,JavaVM工程。
这个工程相当于实现了一个Java虚拟机的所有功能,包括定义虚拟机的数据类型和字长,实现虚拟机栈结构,组织方法区和堆,装载和解析类,实现执行引擎,调用非本地方法,实现多线程,处理异常等。
但这个工程不负责虚拟机在执行过程中需要调用的本地方法的实现。
由于本地方法总是与Java虚拟机实现的底层平台相关的,因此这个工程只负责处理与底层平台实现不相关的部分,而将与底层平台实现相关的部分交给另外一个工程JavaNativeCall工程。
JavaVM工程将开发为动态链接库的形式,有利于别的工程调用这个工程中重要的导出类和导出方法。
第二部分为本地方法实现部分,JavaNativeCall工程。
此工程负责本地方法接口(JNI)的声明和实现。
该工程将开发为动态链接库的形式,有利于JavaVM工程调用该工程中的导出函数。
第三部分为主程序,JVM工程。
该工程负责解析并设置虚拟机运行参数,调用JavaVM工程的虚拟机实现类来启动虚拟机。
该工程为可执行文件。
我们将主程序与虚拟机工程分离,底层平台实现相关与实现无关部分代码分离,使MiniJavaVM虚拟机的实现更具层次感。
同时,也具有了更好的可扩充性
三.1JVM工程
JVM工程负责解析并设置虚拟机运行参数,调用JavaVM工程的虚拟机实现类来启动虚拟机。
JVM工程所要完成的任务可以用图3.1.1表示:
JVM工程只是调用JavaVM工程中导出的JavaVM类,设置其参数及入口类,所有Java虚拟机的运行工程交由JavaVM类完成。
三.2JavaVM工程
JavaVM工程实现了一个Java虚拟机的所有功能,包括定义虚拟机的数据类型和字长,实现虚拟机栈结构,组织方法区和堆,装载和解析类,实现执行引擎,调用非本地方法,实现多线程,处理异常等。
但这个工程不负责虚拟机在执行过程中需要调用的本地方法的实现。
由于本地方法总是与Java虚拟机实现的底层平台相关的,因此这个工程只负责处理与底层平台实现不相关的部分,而将与底层平台实现相关的部分交给另外一个工程JavaNativeCall工程。
三.2.1Java虚拟机的数据类型和字长考量
Java虚拟机是通过某些数据类型来执行计算的,数据类型及其运算都是由Java虚拟机规范严格定义的。
数据类型可以分为两种:
基本类型和引用类型。
基本类型的变量持有原始值,而引用类型的变量持有引用值,图3.2.1描述了Java虚拟机中的数据类型:
Java虚拟机规范定义了每一种数据类型的取值范围,但是没有定义它们的位宽。
存储这些类型的值所需的占位宽度,是由具体的虚拟机实现的设计者决定的。
Java语言中的所有基本类型同样也是Java虚拟机中的基本类型。
但boolean有点特别,当编译器把Java源码编译为字节码时,它会用int或byte来表示bool
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- MiniJavaVM 一个 Java 虚拟机 设计 实现 要点
![提示](https://static.bdocx.com/images/bang_tan.gif)