2007年09月11日

    Jiangli Zhou joined Sun Microsystems in 1999. She has been in the J2ME CDC VM (CVM) team since 2000. Over the years, she has worked on various areas in the VM, including ROMizer, classloader, JIT compiler, etc. Her latest interest is supporting Mobile Information Device Profile (MIDP) on CDC..

原文URLhttp://weblogs.java.net/blog/jiangli_zhou/archive/2007/04/cvm_bootstrap.html

         你可能已经知道CVM是用C写的了。因此,VM启动后发生了什么?在你的main()函数中执行的第一行java代码是什么?这在启动期间是一个连续的过程,我们通常叫这个为启动过程(bootstrap)。这篇文章将解释CVM启动过程和初始化过程的详细情况。

    CVM启动代码是ansiJavaMain()(ANSI适应),它被Cmain()函数调用。ansiJavaMain()做的第一件事转换和预处理VM的参数。因此,它调用JIN_CreateJavaVM()(定义在JNI Invocation API)来创建和初始化CVM。在整个过程中,它初始化所有的VM全局状态,包括系统互斥量(system mutexes),java heapthreadsclassloaders,等等。在VM创建成功后,它装载java程序的主类并调用javamain()方法。

预装载初始化

       CVM支持在编译期间类的容量控制(ROMization)。它通过VM装载和连接类数据。我们有时候叫它类的预装载。这些事情中,首先需要做的是初始化容量类(ROMized class)。每一个容量类块(CVMClassBlock是关于存储java class的远数据中的主要的数据结构),它发现相应的java实例并初始化它们通过填入它们类的块指针,类装载器指针等等。它重申压缩的字符串数据和结构来匹配java实例通过填入字符串值,偏移量和长度等等。注意容量类(ROMized class)不能在java heap中生存。

       CVM中,方法数据结构(CVMMethodBlock)由可变的和不可变的部分组成。不可变部分包括方法名和类型id,方法表的索引等等。一旦被JCCROMizer)或类装载器初始化,它们的区域就不再可写入。可变部分包括调用者指针,编译后代码开始PC等等。作为预装载器初始化的一部分,VM初始化方法数据为每一个容量可变类通过拷贝不可变部分并填入可变部分。

系统互斥量初始化

       象一篇Mar Lamblog一张大图片(CVM地图)CVM有全局互斥量来控制VM子系统的同步问题。这包括JIT锁定,heap锁定,线程列表锁定,类表锁定,装载器缓存锁定,全局根锁定,不牢固的全局根锁定,类型id锁定等等。在整个启动过程中,CVM创建和初始化这些全局锁定。

初始化GC全局根堆

       CVM初始化GC根堆,包括全局根堆(分配JNI全局参考和CVM全局根),不牢固的全局根堆(分配JNI不牢固全局参考),类全局根堆,类装载器全局根堆,保护域全局根堆,类表根堆(所有动态装载类)。全局根堆被GC使用来扫描活动对象。

类型id系统初始化

       VM做的下一个事情是初始化类型id系统和注册一些通用的类型id,比如<init><clinit>,在CVMglobals中的<finalize>。为了防止在运行时重复查找这些类型id

类系统初始化

       下一步是类装载器缓存初始化。装载器缓存被用来缓存所有的<Class, ClassLoader>对,当它们在系统中被装载的时候。

初始化java heap

       CVM使用-Xms, -Xmn, -Xmx选项来规定开始时的最小和最大heap size。它通过VM参数来解决这些大小信息,分配heap的开始大小。Heap可以在运行时增加到最大。

预分配对象监视者和异常对象

      VM创建一个初始数量的对象监视者。这是VM知道的需要的最小数量。异常对象包括OutOfMemoryError, StackOverflowError等等。当没有可用内存的时候根据需要创建异常对象。

JVMTI初始化

       如果支持调试,CVM也做 JVMTI的相关初始化。

JIT初始化

       如果支持运行时编译,CVM需要做 JIT的相关初始化,包括初始化编辑策略,编译器后端,代码缓存等等。

初始化一些系统类

       一些容量可变的系统类需要被VM明确的初始化。首先,它初始化这些系统类不需要线程支持来执行这些静态的初始化。它们包括 java.lang.Class, java.lang.String, java.lang.Shutdown, java.lang.ClassLoader等等。它会创建系统ThreadGroup,ThreadGroup,主Thread对象。在这些线程初始化后,VM调用System.initializeSystemClass()来设置系统属性,stdinstdoutstderr。在这个过程中,它还创建了:

1、  一个参考句柄线程,它询问为决的参考

2、  一个完成器线程来运行完成器。

解析参数

       VM调用sun.misc.CVM.parseCommandLineOptions()来解析其他的参数。在这个过程中,它可以添加用户定义的属性,找到主类名。

       现在,CVM准备好了,为了执行main()函数,首先它需要找到java主类。为次它创建一个系统类装载器(sun.misc.Launcher$AppClassLoader)并用类装载器搜索和装载主类。当类被成功装载,它调用main()方法并开始执行java代码。

2007年09月03日

 Mark Lam has been a virtual machine engineer in the JavaME CDC team at Sun Microsystems for over 6 years. Before joining Sun, he was a real-time embedded systems developer for 6+ years, working on application frameworks, graphics systems, networking protocols, game development, and fault tolerant systems amongst other things, on devices ranging from 64KB 8bit uControllers to 32-bit RISC machines.

原文URLhttp://weblogs.java.net/blog/mlam/archive/2006/11/cvm_stacks_and_1.html

         欢迎继续讨论phonME Advanced VM(CVM)的内部联系。如果你错过了开始的讨论,你可以在这儿看到我通过CVM地图来对VM主要的堆数据结构的一个介绍。今天,我们会深入到java方法的执行和它们在运行时堆中的表现。我说的这个堆是线程里面用来记录活动方法的堆。不是包含APIAPI层的堆。这次的讨论会给你洞察CVM里面java代码执行的控制流(比如任何时候谁拥有CPU)。所有的代码都可以在phonME Advanced的工程文件夹下的src/share/javavm/includesrc/share/javavm/runtime找到。

执行引擎

       CVM中,有一个解释器,一个动态编译器(JIT)。从概率上来说,解释器就是一个大的switch声明。每一个case对应一个bytecode用来执行(在executejava_standard.c文件中CVMgcUnsafeExecuteJavaMethod() 函数)。解释器围绕switch声明循环,直到没有bytecode需要执行。对那些频繁执行的方法(我们一般叫它们热点),JIT会编译这些方法到本地机器代码。编译后的方法会在做bytecode解释的一个地方被执行。

       这儿有许多的方式来测试方法的热烈。CLDC VM使用一个基于取样机制的计时器。象写的这样,CVM使用采样祈祷(invocation)计数在整个解释期间。上面到达了一些开始的热点,方法得到编译。这个问题现在变成了怎么样从解释器bytecode到执行编译后的方法。为了理解这个(所有其他java代码执行的要点),我们需要看看在运行时堆里发生了什么,当java代码执行的时候。

运行时堆

       象前面说的,CVM里的每一个java线程有2个堆:一个本地堆和一个java堆。Java堆一般作为解释器堆。CVM里的每一个线程是一个标示作为CVMExecEnv记录的一个指针。我们通常把这叫做ee,通过ee,我们总能象下面这样找到java堆:

           CVMStack *currentStack = &ee->interpreterStack;

Java

       Java堆是CVMStack的类型(可用看stacks.hstacks.h)。这个堆被组织成一个堆块链表的样子。当堆在CVMinitStack()里被初始化时,它会分配一个堆块。如果一个堆里需要更多的内存,那么附加的块会被分配。因此java堆是可增长的。理论上,堆也能够被缩小,但现在的代码不支持这样做。

       方法活动记录存储在结构(frame)中。基本的类结构是CVMFrame(看stacks.h)。这儿也有CVMFreeListFrame(看stacks.h),CVMJavaFrameCVMTransitionFrameCVMCompiledFrame(看interpreter.h)。所有的这些结构都CVMFrame的多态。注意:尽管CVM是用C写的,它使用了一些对象导向的范例在它的设计中。一些数据结构是多态在制造它的感觉的地方。

       CVMFrame来自frames链表并能跨过堆块。这些链表的头(bottom-most frame in the stack)总是已知的因为它总是堆的第一块。链表的最后一个结构(top most frame in stack)是CVMStack里面currentFrame指针

CVMJavaFrame

       CVMJavaFrame是一个用在bytecode解释器方法里的结构。在调用方法前,VM会把CVMJavaFrame推入堆。结构会根据信息被初始化,信息通过调用CVMMethodBlock *来得到。方法元数据存储在被叫做CVMMethodBlock的数据结构中(通常叫mbMB)。MB的地址被用来作为方法的唯一标示。因此,这就是什么存储在结构中了。结构也包含程序计数器值(program counter = PC)。在这个例子中,PC是一个指向下一个要执行的bytecode的指针(作为方法调用的返回)。当前PC不总是最新的结构。代替它的是保持在解释器循环的本地状态。

       Frame的结构看起来象下面:

                          |-----------------|
      start of frame ---> | locals ...      |
                          |-----------------|
                          | frame info      |
                          | (CVMJavaFrame)  |
                          |-----------------|
       top of stack ----> | operand stack   |
                          | ...             |
                          |-----------------|

       Locals区域掌握java locals(VM spec中定义),操作数堆区域是VM推入推出操作数的地方,这些操作数用来推算opcode,或者是被调用方法的外部(outgoing)参数,或者是刚刚被调用方法的返回值。

       VM speclocals的数量和最大操作数堆容量在任何给定的bytecode方法的时间前是已知的。因此,我们知道如果堆块有足够的房间剩余,在我们把frame推入前。如果没有,那么一个新的堆块将被分配,frame将被推入下一个块实例。

       自从外部(outgoing)参数(对于下一个被调用的方法)存储在操作数堆,操作数堆的一部分成为locals区域的开始,对于下一个frame来说,象下面:

                          |-----------------|
      start of frame ---> | locals ...      |
                          |-----------------|
                          | Method 1        |
                          | frame info      |
                          |-----------------|
                          | operand stack   |
                          |                 |-----------------|
 start of next frame ---> |   outgoing args = incoming locals |
                          |                 |                 |
                          |-----------------|                 |
                                            |-----------------|
                                            | Method 2        |
                                            | frame info      |
                                            |-----------------|
        top of stack ---------------------> | operand stack   |
                                            |                 |
                                            |-----------------| 

这是VM spec说的是一致的,传入的参数开始于0索引的局部区域frame

注意:在CVM中,局部和操作数堆是字空间槽(slot)。在32-bit系统中,这意味着32-bit的内存。堆指针的增长是字增长。这些字能够包含java私有类型(64bit值会占2个槽),或者对象指针。

CVMFreeListFrame

       一个使用freelist frame的是JNI方法的frameFrame的结构如下:

                          |--------------------|
      start of frame ---> | frame info         |
                          | (CVMFreeListFrame) |
                          |--------------------|
                          | operand stack      |
       top of stack ----> | ...                |
                          |--------------------| 
        一个不同是这儿没有引入的lacals或者任何种类的localsJNI方法,传入的参数存储在本地方法的堆栈结构中。这些参数是调用结构(caller frame)操作数堆栈中外部参数的一个拷贝。这个拷贝是汇编调用本地粘和的一部份(在这儿查看invokeNative_arm.S中的CVMjniInvokeNative)。
        另一个不同是操作数堆栈区域只是用来存储对象指针。其他操作数存储在CPU寄存器或本地堆栈(依靠C编译器编译的本地方法)。在JNI中,当你使用NewLocalRef分配局部引用,存储对象指针的堆栈分配一个freelist结构。当你使用DeleteLocalRef释放引用,堆栈slot从一个叫freelist的链表中得到chained。列表的头是CVMFreeListFrame中的记录。JNI方法的MB指针的一个拷贝也存储在这儿。当你分配一个局部引用,我们首先检查freelist的可用引用,如果有一个可用,引用被从列表里移动并返回。如果没有可用,我们碰(bump)顶部的堆栈指针,从操作数堆栈的顶部分配。
        注意,不象Java bytecode方法,我们不知道操作数能分配的最大数。幸运的我也不需要知道。不象Java framefreelist结构能够横跨堆栈块。如果我们在当前块的外部运行,我们简单的添加从其他新的块分配的块到堆栈。
        Freelist其他用法是执行GC根堆栈。GC根堆栈执行使用一个CVMStack和在它里面的一个freelist。因此GC根堆栈是真正假象的对象引用列表,几个GC根能够分配和释放(当你调用JNINewGlobalRoot()DeleteGlobalRoot()方法),freelist frame是很好的执行。

CVMTransitionFrame

       传输结构机制(transition frame mechanism)是一个聪明的诀窍为了得到解释器调用方法,为我们不需要写一大堆代码。它的工作是连同一个特别的指向一个需要调用的目标方法的常量池入口模拟一个byte code方法。常量池入口不少真正的常量,但它可在解释器循环中使用。解释器设置常量池入口指向目标方法的MB。接着,它要求调用4个叫transition假方法(看executejava_standard.c中的这些方法CVMinvokeStaticTransitionCode, CVMinvokeVirtualTransitionCode,CVMinvokeNonVirtualTransitionCode, CVMinvokeInterfaceTransitionCode)中的一个。这些传输方法的选择依靠我们想要调用的类型。

       这个机制用来调用静态初始化方法,另一个为了一步一步的进入java代码里的第一个方法。

CVMCompiledFrame

       最后,我们解释编译结构,如下:

                          |--------------------|
      start of frame ---> | locals ...         |
                          |--------------------|
                          | frame info         |
                          | (CVMCompiledFrame) |
                          |--------------------|
                          | spill/temp area    |
                          |--------------------|
                          | ...                |
       top of stack ----> | operand stack      |
                          | ...                |
                          |--------------------| 

    Java结构,CVMCompiledFrame包含了一个MB指针和一个PC。在这里PC是一个指向编译后代码(指示下一个执行)的指针(返回PC类型)。当VM要调用方法时,它首先检查方法是否编译了,是否是本地代码。对本地代码来说,freelist frame被调用本地粘合汇编代码推入在方法被调用前。“未编译”或bytecode方法有一个java结构推入,在解释器循环中连续执行。如果方法已编译,一个编译后结构会被推入,VM会直接跳到指向编译后方法的指针处继续执行。

On-Stack交换

如果方法已经在循环中被解释很长的时间,我们决定编译它。。。当我们已经解释了一半的方法,那我们怎么样继续执行编译后的方法呢?我们需要的一个特性叫做On-Stack ReplacementOSR)。OSR允许我们替换堆里面的java frame用一个相同的编译后frame

       注意,编译后frame的形态和java frame非常相似。唯一的不同是附加的spill/temp区域。这个区域在方法编译后是已知固定大小的(在framepush前)。这儿的确有其他的不同。一个编译后frame能够有更多的局部比java frame副本当方法内联期间。同样,操作数堆区域的大小也不同。在设计上,CVM JIT保持同样的locals映射对编译后方法作为相应的bytecode。那些从内联方法加入的locals被作为更高的索引被添加。这意味着它能更容易的从java frame到编译后frame。作为编译后frame新特性信息,我们能计算从java frame 信息中不需要的成就

       离开spill区域和操作数堆,一个我们制造的观察是因为自然生成的bytecode从编辑所有的java语言。操作数堆会趋向为空,当循环开始的时候。这在今天的javac编译器情况下有99%是真的(这是一个疯狂的猜测但极可能是对的)Hot循环在我们想要OSR发生的地方。这意味着在操作数堆上没有什么被映射当我们开始循环的时候,我们能够取得优势来做OSR

       作为spill区域,CVM JIT不能产生溢出内容在循环的开始。因此,唯一需要做的一件事是为它在新的frame中储备一些空间。因为这些,我们能替换hot loops,用同样编译后的替换部分解释器。

       注意:CVM只支持java frameOSR连同他们对等的编译后frame,而不是其他方向。其它方向中的OSR是值得注意的更多的困难和更多的招数导致更多的开销而不适合JavaME系统。这会是一个区域,那些团体能够选择调查在未来,如果它希望。这儿有一些有趣的高级的事能够和OSR相反的来做,如果我们从来没有接触它,我为在其他时候离开。

下一个是什么?

      我们已经介绍了java stack结构和CVM。明天,我会简要的谈论关于本地stack frame(那些嵌入式程序员应该很熟悉它)和更多的有趣的。我会谈论他们之间的相互影响,因此,请看这个讨论的第2部分。

祝有美好的一天!!