Java虚拟机分析 1 Java虚拟机 Jav a不仅是一种跨平台的语言,而且是一种新的网络计算平台. 该平台包括许多相关的技术,如符合开放接口标准的各种API、优化技术等. Jav a技术使同一种应用可以运行在不同的平台上. Jav a平台可分为两部分,即Jav a虚拟机( Java vi rtualmachine,JVM)和Java API类库[1 ] .在Java 平台的结构中, JVM处在核心的位置.它的下方是移植接口.移植接口由两部分组成,其中依赖于平台的部分称为适配器. JVM通过移植接口在具体的平台和操作系统上实现. 在JVM的上方是Jav a的基本类库和API. 利用Jav a API编写的应用程序( applica tion)和小程序( Jav a a pplet )可以在任何Java 平台上运行而无需考虑底层平台,从而实现了Java 的平台无关性. Jav a程序的执行过程是: 首先,源程序( . jav a )经过编译器编译后生成由字节码组成的类文件( . class) . 然后由类装载器装入来自类文件的字节码,经过检验器安全验证后,由JVM读取字节码, 转换为特定平台的指令,并在相应的C PU中运行.JVM是Java 程序运行的心脏,了解并分析它对于实现Java 虚拟机是非常重要的. 作者以Kaf fe(一种Jav a虚拟机的实现)的桌面版本为例,对虚拟机进行较深入地分析. 2 Java虚拟机的运行过程 Kaffe 是一个JVM以及类库的实现,这种实现采用开放源代码的形式[2 ] .任何Java 程序都是由若干个类组成的,其中只有一个主类. 源程序是一个. java 文件,它经过编译后生成若干个. class 文件. . class文件的个数由程序中类的个数决定[3 ] .JVM在执行某个程序时,首先从主类的main方法开始.所以在虚拟机运行的时候,首先要给出主类的名字,然后虚拟机会根据用户提供的名字按照系统设定的class路径寻找该类,并将其装载、链接、初始化(如果找不到该类,则报告错误并退出) . 同时,该类的初始化有可能引起其他类的初始化(因为在一个类初始化之前它的父类必须被初始化) . 初始化完成后,虚拟机便寻找主类的main方法,并按照用户为main方法提供的参数执行. 当main方法的代码成功执行后,虚拟机卸载当前线程并退出. 此时整个程序的执行过程完毕.用户可以自己设定一些JVM 的环境参数,虚拟机程序执行的第1步是处理用户对这些参数的设置,然后开始执行程序. 例如,可以利用命令行kaf fev ersio n获得这个虚拟机的版本号,也可以通过kaf f mx 设置堆栈的最大容量. 其他参数可以通过help获悉.以下是一个Kaf fe执行过程的实例:欲执行程序hellow o rld. java,其源程序为: ∥ hellowo rld. jav apublicclass hellowo rld {public sta tic v oid main ( St ring args [ ] ){sy stem. out. println (″hellow o rld″) ; }} Kaffe虚拟机是用C语言实现的, 任何C语言程序的执行都是从main函数开始的, Kaffe也不例外. 整个虚拟机的执行流程是:.① 程序执行过程如下: 按照该流程,首先应该获得虚拟机环境参数的缺省值. 获得该缺省值的功能由函数JN I GetDefaul t JavaVMIni tArg s( & vmargs ) 实现. 该函数可将结构KaffeJavaVMIni tArg s拷贝到vma rg s中.② 处理命令行中用户设置的参数. 该功能由函数static int o ptions(cha r* * )完成. 该函数的参数就是main ( argc, a rgv )中的a rgv , 它的返回值是一个整形量,表示该函数处理的参数量.③ 为虚拟机进行初始化、分配内存. 该功能在main中通过调用JN I Create Jav aVM(& vm,& env,&vmargs)实现.该函数中参数vmag s是已经被赋初值的虚拟机参数集合,该函数在完成虚拟机的生成工作后将指向JVM的指针存放在vm中. 虚拟机生成的工作主要是申请内存,然后将其中的参数进行缺省设置.④ 根据用户在命令行中提供的主类名寻找主类并将其装载、链接、初始化. 这是整个虚拟机执行过程的重点. 在函数main( )中是通过调用kaffeFindclass( )实现的. kaf fe Findclass( )起到了调度的作用,欲处理的类可能是数组或类. 因为在Java 中数组是被看作类处理的. 函数loo kupclass( )的功能是寻找指定的非数组类并完成它的初始化.lookupclass( )的流程的. lookupclass* 源程序如下 :Hjava lang Class*lookupClass( const char* name, error Info *einfo ){Hjavalang Class* cla ss;Ut f8Co nst* ut f8;uf t8= ut f8Co nstNew ( name, - 1) ; /*将欲处理的类的名字转化为U tf8形式* /if (! utf8) {postOutOfMemory ( einfo ) ;return 0;}class= loadClass( ut f8, N U LL, einfo ) ; /*调用loadclass( )完成类的装载* /utf8ConstRelease( ut f8) ;i f ( class != 0) { /* 如果类已成功装载,调用processclass( ) * /if ( processClass ( class, CSTA TE COMPLETE,einfo)= = t rue) {return (class) ;} } return(0) ;}Lookupclass ( )函数中所调用的两个子函数loadclass ( )和processclass ( )的功能分别是装载和链接、初始化.loadclass( )的流程:loadclass ( )首先检查classent ry 表, 该表记录已经被装载类的情况. 假设方法1用到类A,则装载类A,若方法2也用到类A,方法2就不必再装载该类. 因为方法2通过classent ry 表得知类A已经被装载. 每个classent ry表项的内容如下:typedef st ructclassEnt ry {U tf8Const* name; ∥该表项所表示的类的名字Hjav a lang ClassLoader* loader; ∥该类被装载时所用的装载器Hjav a lang Class* class; ∥指向该类在内存中的指针st ruct cla ssEnt ry* next; ∥指向下一个表项的指针st ruct iLock* lock; ∥实现查询或更改该表的同步}classEnt ry;Loadclass( )查询classent ry 表,看所指定的类是否已经被装载. 若装载,则返回指向该类的指针;否则,创建一个classentry 表项、该类装载完成后使ent ry- > class指向该类.查看它的父函数loo kupclass( )是否给出了具体的装载器. 装载器是Java 中类cla sslo ader及它的子类,它们的功能是完成类的装载.如果给出了装载器,就执行该装载器类的方法. 如果loo kupclass( )没给出具体的loader,则使用findclass( )函数. 该函数寻找指定类,并完成它的装载,然后返回指向该类的指针. 如果loader或findclass( )执行不成功,就会给出错误信息并返回空指针.Loadclass( ) 完成后, lo okupclass( ) 会调用Processclass( ) , 完成对已装载类的链接和初始化.装载、链接、初始化都完成后,对编译后的main方法的字节码逐条加以解释. 这里虚拟机解释的是字节码,字节码是Jav a源程序经过编译后的结果. 如果把虚拟机看成是一个真正的机器,那么字节码就是这台机器的汇编语言. 按照Sun的Java 虚拟机规范, JVM拥有大约220条指令. Jav a源程序经过编译后所生成的类文件相当于Java 源程序的汇编程序,这里的汇编指令是虚拟机的指令. 在Kaffe中,逐条解释字节码是通过函数runvi rtualmachine( )实现的. 在kaffe. def中定义了每一条虚拟机指令的解释程序[4 ] .runvi rtualmachine( )的执行过程是一个循环体很大的循环,该循环如下(忽略异常):do {fetch anopcode;If ( opera nds) fetch o perands;ex ecuter the action fo r the o pcode;}while( there is mo re to do) ;kaf fe的实现也是如此,取一条字节码指令后会进入一个大的case 语句中,使其与指令集中的220条指令进行匹配,匹配指令的解释程序将被执行. 3 面向堆栈的体系结构 在一般计算机中,操作数放在寄存器或者存储器中. 而Jav a虚拟机采用了面向堆栈的体系结构.JVM中的堆栈区用来存储操作数和中间结果,传递参数并返回结果,同时也跟踪方法的执行状态.方法的执行状态称为当前方法的堆栈框架( stackf rame) ,它保存方法的当前执行信息(局部变量、中间结果等) .堆栈框架包括3个部分: 局部变量区、操作数栈和执行环境区. 局部变量区包含当前方法用到的所有局部变量. 操作数栈是字节码指令的工作区,操作数栈顶指针寄存器( o ptop regi ster)存放栈顶地址.执行环境区维护栈本身的操作,执行环境寄存器( f rame register)中包含指向它的指针. 4 结束语
Java 是一种解释型语言,逐条解释字节码的过程是比较耗时的,这导致了Java 的致命缺点—— 速度慢. Syma ntec, Borland, Microsof t等大公司都在开发JIT(及时)编译器,程序开始执行之前把字节码编译成本地机器码,这样就用字节码编译器代替了翻译器. 采用这种技术的Jav a虚拟机性能大幅度提高,通常比翻译器的速度快5~ 10倍. 也许将来JIT编译器会成为Java 虚拟机整体的一部分,从而使Java 虚拟机性能满足应用的要求.Java 应用需要解决的主要问题之一是加快虚拟机的运行速度. 本文中以一种JVM的源代码为例,分析了虚拟机的运行过程,意在对改进它的运行机制起到一些作用.
|