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部分。

祝有美好的一天!!

2007年08月16日

 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/the_big_picture.html

 

         亲自的,当你接触一个新系统,第一件事是你想要搞清楚每一个东西是怎么结合在一起的。如果你像我一样是个虚拟思考者,最好的一条路就是画一个图表来说明你觉得重要的东西以及他们之间的联系。嵌入式系统的一个例子,以我的经验,知道内存里有什么在哪儿,系统资源是怎样使用的也同样重要,因此我画了一个数据结构的图表,下面:

CVM的世界参考物?

下面是怎么来读这张图

根数据结构

       CVM的一个设计标准是可重新启动,甚至当你在没有进程的OS上运行它。没有进程需求的可重启性,我们能够释放所有分配的内存。为了让生活更容易(它总是一个很好的实践),我们必须确保所有的数据从单一数据结构书的根上是可获得的。这个根数据结构是CVMglobals,你能在上面地图的左边看到。你会在globals.hglobals.c里找到CVMglobals的定义(也可以在这个文件里找到CVMGlobalState)。看下CVMglobals,你会发现它是一个系统全局数据结构的集合。保证全局在一个地方可以让它更容易恢复已知的初始值。例如:使用memsetting把所有的值设为0(在外面清理了所有的数据后)。

垃圾回收和Java堆栈:

       从全局,你可以找到一个内嵌的结构,它把握GC的配置和管理信息(CVMglobals.gc)。从这些,你最终可以得到Java heap。

       CVM有可插件的GC体系。插件是编译时的可插件性,不是运行时。它允许在CVM里根据实验来试验GC。当前,唯一一个有品质保证的CVM的GC是世代的(generational)GC(在这儿这儿可以看到GC的细节执行文件)。

       所有的java对象,也就是所有从java.lang.Object扩展的对象,都从java heap上分配。只有一个例外就是ROMized Java objects。它们存在于全局数据。javaheap自己从C heap上分配。所有其它数据结构都从全局数据(i.e. .bss, .data, or their equivalents)或从C heap分配。

JIT和编译后代码

       CVMglobals为JIT(CVMglobals.jit)掌握配置和管理记录。通过树,你最后会找到JIT代码缓冲。这个代码缓存当前是固定大小的(通过运行时可配置)它在VM启动时分配。一旦它被分配,它的大小不能被改变。

       当java方法被JIT编译,编译器生产的比特(通常作为编译后方法提及)会存在代码缓存中。编译后方法的原数据(meta-data)(由JIT生成)也会存储在代码缓存中。因此,代码缓存的大小会规定,间接的,有多少方法能被编译。

Java对象和类

       当类文件装载到内存,内容主要被解析和组织到一个最佳的结构,它从C heap里分配。这个结构叫CVMClassBlock,它掌握所有类的元数据。原数据包括常量池,类属性,域和方法信息,bytecode等等。对每一个CVMClassBlock,有一个从java heap分配的java.lang.Class的实例。一旦一个类已经被装载,它们总是作为一对存在。类块(classblock)有一个参考对一个类,反之亦然。当一个类unload时,它们会一起被释放。

       CVM里的每一个java对象有一个2个字的头。第一个字包含了一个指针指向类块。无论如何,这个头对java代码不可见,它只对VM里的C方面可见。注意:自从java.lang.Class扩展自java.lang.Object后,类实例也有2个字的头

       查找关键文件在objects.h and classes.h。 看

Java线程

       要执行任何东西,VM必须有线程。每个java线程由CVMExecEnv表现(通常缩写成ee)。在VM,本质上ee是 线程的标示符。所有线程操作请求作为当前执行的线程的ee的参数。看interpreter.h 和 interpreter.c 

       这儿有一个一对一的映射关系在ee和java.lang.Thread实例之间。一旦线程被初始化,它们2个就作为一对存在。

       这儿有一个一对一的映射关系在ee和JNIEnv之间。JNIEnv是一个嵌入的,作为ee的一个域。ee和JNIEnv之间的映射地址的基本需求只是偏移调整。

       所有ee被装载在一个链表里。这个链表的头是CVMglobals.threadList。主线程的ee被做为嵌入域在CVMglobals被分配。

系统互斥

       VM线程列表的操作需要被同步。在VM里的其他子系统和资源都一样。同步一般都由CVMSysMutex(看sync.hsync.c)。在VM启动时有几个系统互斥量(sysMutexes)被分配。这些互斥量只对VMC代码可见。只能被C代码使用。

       每个互斥量都有明确的目的(CVMglobals.threadLock用来同步线程),它是有序的。为了防止死锁,系统互斥量只能在增加序号时被锁住。当CVM使用断言建立时,升序可以被断言。

Java执行堆

       一个执行的线程必须有一个执行堆。在CVM里,每一个Java线程有2个物理堆:一个本地堆,一个java堆。本地堆由系统分配,用于C代码执行。它掌握本地代码的活动记录(堆帧 stack frames),VM代码包括解释器循环功能。它掌握所有JIT编译后代码的活动帧。

    Java堆(也叫解释器堆)用来保存所有java方法的活动记录。每个java方法一执行,一个帧会推入堆中。堆和帧数据结构定义在stacks.hstacks.c中。

    如果你在执行java方法时dump一个本地堆的跟踪,你会看到C代码的堆帧(stack frames)和解释器循环。如果你dump一个java堆,你只能看到java代码的堆帧当它们被调用时。如果你有一个本地方法在执行链(invocation chain),你可以看到本地堆和java堆的堆帧。这是因为本地方法是C功能和java方法的和。

GC根和根堆

GC阶段,CVM被称为一个精确的VM。这意味着在GC时间,我们能够确切的知道所有的对象指针在系统的哪个地方。这和需要你猜测某个内存片包含对象指针和一些类似对象指针的随即数据的老式GC系统有鲜明的对比。

      VM里所有能获得的对象能够通过跟踪被叫做垃圾回收根树的对象参考树来找到。这个树从一个根参考开始。这些根参考本质上是全局的。用来存储被称为根堆(root stacks)的数据结构。有一个例子是CVMglobals.globalRoots。准确的说,这些数据结构不需要成为堆。它们被作为链表来使用。无论如何,我们的java堆数据结构有道具来实现GC根堆的需要,不要请求我们写附加的代码(为了代码效率),因此我们只使用堆。

       如果一个对象不能通过跟踪根树来找到,那么对象是不可到的,它可以被GC再生。

       注意在穿越树中,任何的点,一个节点都可能是新的子树的根。因此,term root或者GC根有时被用来指向一个对象指针/参考,它总能通过根查找来找到。GC根能在根堆里找到,在线成执行堆,在对象和类域里。

终了

      给了你们上面的主意足够让你们了解CVM里最主要的数据结构。注意:我告诉你们的大部分事给了你们一个很好的概念模型。实践上,这儿有一系列的原因来导致异常。某些时候,这些异常会打破规则。另外一些时候,他们看起来象是扩展了规则。为了保持事情简单,我忽略了它们。当我在谈到每个子系统或特殊的数据结构的时候我会详细讨论它们。

       在上面,我忽略了一些有趣的细节,比如为什么从C heap 分配数据结构相对于java heap。几天或几周后,我会放大CVM的子系统或数据结构,详细的讨论它们。这包括机械的细节作为设计体系。

祝有美好的一天!!

2007年08月10日

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/multitasking_th_1.html

 

         今天,我在java.net论坛看到了这个讨论。它让我感到惊奇,假如所有人都意味着同一个事情,如果当他们在谈论java平台的多任务时。因此,我决定今天暂停讨论CVM,开始多任务(这也与phoneME和CVM相关)

不承诺:在开始前,我必须阐明我的观点只代表我个人,不代表Sun,我的雇主,我在Sun的同事。

什么是多任务?

       严格的说,多任务意味着同一时间能做多个事情。对JAVA平台,这意味着能够有同时执行的代码。JAVA平台已经支持多任务。因此,这个问题是什么?人们想同时更好的运行多个程序。这到底和从不同的线程调用程序的main()函数有什么不同?不同是,程序想要自己的世界和所有的资源。简单的运行它们在不同的线程可能只有边缘效果,当一个程序能同其他操作交互的时候。因此,一个多任务JAVA平台需要能够分别隔离那些程序。那么,我在那里听到的这些特性点和行为?为什么这是现在操作系统的普遍应用。

       因此,当人们想要多任务的时候,我想他们是想要求一个JAVA进程,JAVA平台代替这些系统相关的角色。让我们看看多任务特点在系统中,看看这些在JAVA平台下证明了什么。我们应该关心人们想要的这些特点。让我们先从系统开始。。。

系统透视

       如果我们为JAVA平台处理,他们应该有下面的特点:

特点                            JAVA平台包含

同时(Concurrency)        多程序能同一时间运行在同一设备上。系统能够

                  保证程序间资源的合理分配

隔离(Isolation)          每个程序必须有自己的VM环境。一个程序不能同

                  其他同时运行的程序冲突。在错误的程序环境中,              

                  错误和不良行为能被检测

可靠的终止(Reliable Termination)  当程序出错,程序管理器必须能够中止程序并可靠

                  的回收资源

效率(Efficiency)          减少资源使用的冗余,在程序间最大限度的共享

                  资源。在JAVA VM对每个程序初始化的时候减

                  少冗余

JSR 121 Application Isolation API 定义了API来管理这些特点。同隔离成为系统处理。无论如何,有API并不是就有了执行。

人们想要什么

       这儿有一些解释关于人们为什么想要多任务。这些例子不一定能详细,但我想它们很现实,能够马上解决这个问题。

问题1:你工作在一个运行不支持进程简单嵌入式系统的设备,你不能提供系统升级因为所有的本地代码你都已经投资。升级意味着移植所有的原始代码到新的系统,这非常昂贵。你已经为所有的系统代码的许可付了钱。但是你想同时在设备上运行多个程序,程序由你不能控制的第三方提供。用户甚至需要运行一些从网络上下载的程序。不幸的,如果你的设备crash或者开始运行缓慢,你的用户将责备你而不是程序,尽管他10岁的儿子是从不安全的站点下载的程序。

       那么,你考虑。。。JAVA平台是一个虚拟运行环境,为什么你不仅仅让它保持,当程序要向其他程序或系统做坏事的时候?问题解决!

问题2你写了一个浏览程序,需要运行JAVA程序作为插件。当你不需要它时,你可以让它到一边去而不消耗资源。你想JAVA VM提供这些。但现在,你的系统本地API不能给你一个方便的方法让你做这个。自从JAVA VM被作为一个库被装载来匹配你的浏览程序。如果你使用系统特性来赶走VM,你将结束你的浏览程序,或者让它僵死而挂起。这会让你看到不好的东西。

       那么,你考虑。。。JAVA VM是一个装载程序的东西,对吗?为什么你不把程序结束并让它自己清理干净?问题解决。

问题3你需要同时在你的设备上运行多个java程序,但系统不让java程序共享资源。。。或者资源不够。

       你考虑。。。不少所有的java程序都需要同一个class libraries来运行?为什么JAVA VM不让他们共享内存中的同一副本?问题解决!

不够快

考虑这些。。。

       对问题1,为什么执行器不考虑执行隔离在它自己的程序里,或者改善嵌入系统来做这些?毕竟JAVA VM也是软件的一块,它也从它的程序里被启动。如果它能够改善它的系统隔离能力,那每一个启动的JAVA VM都能有它自己的进程,它的问题就会被解决。

       对问题2,为什么执行器不考虑执行,在浏览程序里,一个资源跟踪系统捕获所有被JAVA VM使用的资源和正在运行的程序?如果浏览器能跟踪所有它的资源,那么它能杀死VM和程序线程,收回所有它自己的资源。

       对问题3,为什么执行器不试着改善系统(或者询问系统提供商)来多更好的工作在共享普通资源?

       对所有3个问题的答案:这是很困难的。。。非常困难。转移这些问题到JAVA平台不会让它变容易。现在好了,这儿有几个信息,JAVA VM有可能很高机会完成这个目标。但本质上,这个问题没有变容易。

       我只想让你考虑这个问题的复杂性。当你自己解决了一个问题,你的感谢为它改变。多任务不是一个价值不高的特性,Sun或其他JAVA平台提供商能够发出其他弹点在他们的成品线路图。执行多任务是一个主要承诺

       Sun已经发出了2个多任务虚拟机解决方案对CLDCCDC,让我们看看它们怎么工作?

引擎盖下是什么?

       上面3个举例的每一个是多任务的3个不同的需要:隔离(isolation)、可靠的终止(reliable termination)、效率(efficiency)。在现实生活中,这些特性的几个或全部都需要集中在一起。

       我不会同时谈论这些特性,因为它们不是解决方案的难点。JAVA平台已经支持同时执行线程。它不是太多延伸对建立程序并发。除非你需要一些特殊的进程安排道具,我会离开这个讨论。

隔离(isolation

       CLDC不支持一个本地接口对本地方法。在实践中,大多数的CLDC程序是MIDlets,它是100%的纯java代码。因此,CLDC MVM只需要处理java statejava bugs的隔离

       java state方面,我的意思是类静态域(class static fields),一个实例被系统库使用。系统库被所有的程序使用。不需要隔离,每个程序都能够在系统库里看到其他程序的操作。

       一个java bug的例子是僵死状态。一个程序有2个线程同步操作2个不同的对象在对立面。结果,每个线程将会锁住来等待另外的线程来释放其它的锁定,每个线程将永远锁定来等待其它线程。无隔离,其它程序线程可能会同步同一个对象并永久的锁住。因此,一个行为不好的程序问题会导致其它行为良好的程序。

CLDC隔离

       CLDC MVM的解决方法是复制(replicating)所有静态域在系统库中。每个隔离得到它自己的一份拷贝。结果,类/静态初始化需要对每个隔离运行一次,来保证静态拷贝的初始化是隔离的。在这个机制下,每个隔离会创建它们自己单一对象的拷贝在系统中。

    CLDC隔离同样涉及一些小把戏来确保不同的程序得到不同的锁定实例当同步在ROMized 类和字符串实例这些不能在 JAVA VM中复制的。实际字符串(interned strings)被同样的对待。

    拥有java 状态自动隔离结果在java bugs的隔离中。如果一个程序僵死,其他程序不会受到影响因为僵死程序的java state对它们不可见。

    这儿有另外一种java bug:恶意程序有目的的做一些拒绝服务(DoS)攻击对其他程序。一个例子是消耗完所有可用内存让其他程序不能运行。CLDC MVM依靠每个隔离实行资源配给来解决这个问题。如果一个隔离试图获得额外的资源,它会得到一个内存不够(OutOfMemoryError)的错误,而其他程序仍可以分配内存 。

CDC隔离

       CDC需要为本地方法支持java本地接口(JNI)。因此,CDC VM需要为每个本地状态或错误提供隔离。本地状态,我的意思是本地代码的全局和静态变量。本地错误包括那些残留在内存中的废指针,分裂的块(segmentation faults),非法的指令等等。

    掌握这些是在系统进程中隔离本地代码的唯一出路。。。对每一个程序。CDC VM做这些是完全正确的。它使用特殊的处理类型叉子(fork)来生产新的隔离。每个隔离运行在它们自己的进程中。CDC MVM当前仅仅在linux和solaris下可用,因为这有这些系统提供叉子(fork)兼容。Java级的隔离是进程自动掌握的。

    象DoS攻击应付资源缺乏,CDC VM应用系统去执行配额假如需要。这没有什么不同和本地程序对应资源缺乏的解决方法来说。

可靠的终止

CLDC 隔离终止

       CLDC MVM提供自己的线程库。当它需要终止程序时,它简单的选择不计划更多的程序,并取消所有的参考来隔离。因为所有的程序都是100%的纯java代码,它们的资源会通过垃圾回收(garbage collector)来清空。一些资源有本地副本,这些会被那些由GC触发的私有终结器(finalizers)来清空。注意:终结器不是CLDC规格的一部分。它是一个VM提供的一个被系统库使用的执行。

CDC 隔离终止

       CDC MVM里的隔离有自己的进程,终止是简单的杀死各自的进程。

效率

CLDC 普通资源共享

    CLDC MVM,所有的隔离作为线程运行在同一个物理VM,它们自动共享所有常量数据。这包括类的元数据(metadata)(比如常量池,属性),method bytecodes,ROMized对象和内部字符串。

    每个隔离从java堆中得到一个配额。通常,从java堆中分配的资源不是共享的。无路如何,这儿有更少的内部破碎因为分配的隔离来自同一个堆。

    开始一个新的隔离不需要VM完全重初始化。无论如何,系统库的类初始化需要重运行。

CDC 普通资源共享

       CDC MVM,隔离是从一个共同的进程分离(fork)出来的不同的进程,它们共享作为数据的只读内存页,不需要写入。CDC MVM采用几种技术来帮助系统最大化共享。

    在CDC MVM开始一个新隔离也不需要child VM完全重新初始化,但工作线程需要重生因为进程分离(fork)不和线程工作。象CLDC,类的初始化也需要重运行。

CDC MVM是真的MVM

       如果CDC MVM应用它的很多特性在系统进程,它和其他简单运行完整CDC VM的不同实例在进程中有什么不同?MVM被优化来允许有效的资源共享,但单个完整的VM做不到。这些优化也导致了一些性能上的开销但不会在单个完整VM上。什么?MVM有性能开销?为什么?是的,你想它会免费?你在2个隔离之间共享更多,开销也会更多。反过来也一样。但通常开销只有23%(如果我记得正确)。这可能或不可能成为你需要性能上的重要依靠。

       MVM和程序管理器一起来管理隔离,和单个完整VM运行在不同进程不一样,你有你自己的进程。

       在练习中,不是所有的隔离完全可用因为系统进程也会使用。比如,一些物理资源(硬件和系统)不能复制。因此,访问这些资源需要在隔离之间协调以至于它们不能相互一起。一个例子是绘画屏。Java类库(或他们的本地代码)需要修改来允许隔离间的协调。这个协调有程序管理器控制。

       物理资源的协调由CLDC MVM来执行。

我能在CDC中使用CLDC风格的MVM吗?

       技术上可以。。。但是这儿有个问题。CLDC风格的MVM不能做关于本地代码隔离的事情。那么,让我们看看我们只需要在环境中配置VM在我们保证那儿没有本地代码在程序中(或者我们拒绝有本地状态的程序)。那么我们能使用CLDC风格的MVM了吗?

       是的,但这儿仍然充满了附加的挑战。CDCVMCVM)运行在完全pre-emptive的本地线程上。调度程序在系统中,不是VM。因此,VM在线程调度上没有控制权(如果系统不提供这个机制。。。通常,他们不提供)。因此,近似于CLDC不能进行可靠的终止工作。

       我们还记得老的Thread.stop()API。但我们已经知道它有一个问题。但是,我们在这儿谈论的是100%的纯java代码程序。我们说我们不会让任何的本地代码运行。我们有CLDC风格的MVM来隔离java状态。这会让Thread.stop()的问题走开吗?现在让我们的任务终止?

       是的,我们可以让它工作。VM可以设置一个标志来在隔离中指出一个终止状态来终止。检查这个终止状态需要插入一系列的地方:VM InterpreterJIT编译器生产的编译后代码,本地代码中的循环。这些保证线程被终止

       在这些检查点,如果终止状态被检测到,一个未捕获的异常将被抛出。VM会忽略try-catch块当它出来这个异常。这确保程序不能捕获异常并防碍线程被终止。异常会导致java和本地堆栈展开。

       VM能够展开堆栈框架为java bytecode methods。但本地方法会要求本地方法检测异常并返回。因此我们不允许任何程序的本地代码,我们只在系统库中处理本地代码。每个本地方法会被修改来检测终止状态,确保它作为异常的替代返回

       另外,所有本地方法需要被检查以确保他们释放了所有的由他们方法中分配的资源。这些资源必须被清空在方法返回异常堆栈展开前。一些本地资源没有被分配和使用在一些本地方法。对这些类型的资源,我们需要确保他们同finalizer java对象的关联,因此他们能被取回当对应的java对象被GC时。

为什么我不能有自己的CLDC风格的MVMCDC

       你是不是感到疲惫?我们需要做这么多步骤为了在CDC中执行CLDC风格的MVM无疑是冗长的。现在考虑CDC库同CLDC库的复杂度和大小。因为所有本地库将被适当的修改来迎合CLDC风格的MVM正确的工作。这个执行努力是相当的高。风险也相当的高。

       另外,如果VM部署在一些中间设备堆(例如对机定盒的MHP中间设备),所有这些中间设备的本地代码也需要同样的处理来保证CLDC风格的MVM正确工作。

       因此,这个风格的MVMCDC来说执行难度是相当的高。Sun究竟有没有试验代码在CVM上来执行这种MMV。因为执行的花费和风险包含本地代码部分,这些代码从来没被完成。但是,它和快会被开原在下一个phoneMe Advanced版本,欢迎每一个感兴趣的人来完善它。

JavaSEMVM

       CDC风格的MVM可能或不可能在JavaSE中运用。这些VM的结构不同。但是,他们可能或不可能有利于这个途径。JavaSE VM的类共享特性已经是这个方向的一步了。

       CLDC风格的MVM。如果你明白它同CDC的不同,你可以看到它已经很大程度上接近JavaSE了。这是因为JavaSE有相当大的类库。但是,只有这些库中的本地代码会被贡献不同。幸好,JavaSE库的大部分是纯java代码,但这足够让这个任务较少的让人畏惧。

如果我不需要所有的隔离特性?

       CDC,你已经有命名空间隔离通过使用类装载器(classloader)。这儿有部署在基于同步线程和类装载器命名空间隔离的系统。但是,这些系统经受不同的缺点来自真隔离的。一个通常的抱怨是一个行为不当的程序能够坠毁整个系统,所有的程序都同它一起坠毁。

       已经有厂商提供MVM解决方案。我总是希望他们能做一些工作象Sun在分析问题,或者他们提供一部分的解决方案。但是我没有使用他们的VM,我不知道他们的MVM是怎样完成的。

这对你意味着什么?

我希望你现在理解这个令人畏惧的任务包含在建立MVM解决方案中。我明白当你需要MVM的时候它还不可用,知道去执行这个这是很困难的。

但是,现在java平台开源了,如果这些特性对你来说非常重要,请贡献在VM领域。如果我们为这个工作在一起一点时间,我们能让MVM更快实现。

祝有美好的一天!

2007年08月06日

   

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/c_further_with.html#more

         我已经谈论过一些关于CVM的深奥的知识,并且我想是时候说一些真正的技术了。因此,我花了昨天大部分的时间来制作了一个关于CVM的结构图来向你展示它的结构,但它比我想象的要长。结果,昨天没有放上blog。今天有希望能把它完成,并在周一补写到blog。它看来像放在坚果里的CVM

       另外,我使用 InkScape 来存放我的CVM结构图,我不知道这是不是最好的,但它适合做这个工作。因此,我想我在这给一个说明万一其它人也在找这样的工具。我使用 InkScape 是因为我在SVG里渲染CVM结构图,因此我需要缩放它来匹配任意你需要的解决方案而不用考虑细节。但我也发现我的浏览器不能快速的显示SVG格式的图片(可能我没有导出正确的格式),如果有人发现你的浏览器不支持SVG格式,请告诉我,我会把它转成PDF格式。

       顺带说一下,我想感谢留言的2位朋友,这让我知道我没有对牛弹琴。

为什么用C来写CVM

       这是CC++之间的选择?像我在前面文章里指出的一样,CVM的体系非常匹配对象类。使用C++已经是一个选择。我们选择C的原因是因为C编译器比C++编译器在嵌入式领域有良好的实用性。我提醒你可移植性是CVM的一个主要目标,也是为什么它在嵌入式领域能够生存的原因。有代表性的,当硬件更新,它总是和C编译器一起的,而C++编译器可能会或者可能不会更新。我们希望Java平台能够在任何平台运行,事实上这和选择C是一样的。

       第二个要注意的地方, 我们发现一些C++编译器总是生成非常低效的代码在一个周期(23个周期),这对任何嵌入式软件都是不好的。现在,在你跳到结束前,我不认为C++原因本身是低效的。私下的,我是一个C++的爱好者,我知道它怎样让你写出真正一流和高效的代码(像计算编译器协作)。或者臃肿的代码。我的建议是在这个时候,一般的人们不要过多的关心C++在给了你多少的工具链(和C比)。不要说任何C++比其它好很多的话。事实上,C++有一个坏名字,我想这非常不幸。

       照顾好自己,7年前,CVM就决心做些什么。低效的代码生成在34年前被察觉,或许这些关于实用性和效率的问题已经被修复了。

       其它我想要说的关于便携性和性能的在下面

便携性的一个注解

       我在前面提过,CVM的代码是由在shared目录里最高的代码密度组织成的(相对于特殊平台)。给你一个特别的概念,在2003年,我们在shared目录和platform目录为动态适应编译(JIT)测量代码线作对比。在这种情况下,shared部分包括 RISC层在 portlibs 目录,开始共享代码的比率是80-90%,保留CPUOS的特别代码。大多数由汇编代码组成的非共享代码生成的特定CPU体系的常规程序。如果我们考虑VM空闲因素,这个对共享代码的比率会更高。我们没有实际测量这个,但如果我乱猜的话,我说他会上升到95%

       一个汇编程序非常小的子集作为编译器代码和VM休眠之间的过渡是非常必要的。保持所有的焦点在特殊CPU体系的优化上。一个好消息是,除了少量的粘合代码外,CPU特殊的优化代码都是可选的。这意味着你不需要为JIT执行所有的命令来保证操作的正确。代码的共享部分(小范围汇编代码)对性能的提升仍然有重大的意义(50~70%的提升)。附加的优化根据需要或VM开发者的时间

       另一个要注意的问题是关于CVM体系的。一个 VM开发者必须能够完成一个CVM的完全最小效果的移植,但仍然会得到许多性能上的原因。那么,如果时间允许,尽可能的调整和优化以得到附加性能的提升。这就是为什么我们大多数的更新和提升在用C写的共享代码中。

       提升仅仅是一个CVM build的解释(比如禁止JIT),它会带来少许的成就。这儿只有一个常规汇编程序需要编写。这个程序通常被称为“本地调用(invokeNative)”,它的责任是成为本地代码和解释器的粘合器。它的全名是“(CVMjniInvokeNative)”。在这儿有例子。如果你做linux移植(或其他系统),那么你会有个很好的开始。如果不是,你将执行HPI,但你可以使用其他已经存在移植的相关代码。

       总的说来,CVM容易移植不仅仅依靠减少必须进行的移植工作量。(共享代码对平台特殊代码的高比例),它的目标总是允许移植成就相对于现阶段。对开发者来说,为了适合开始时间它是很容易分离的。它总是增加系统的易测性。

性能的一个注解

       我的职业教育背景是硬件。因此,随时我听到的关于某些软件的创新都会让我开始提高系统的性能。怎样的软件能使硬件超过它现在的性能运行的更快?答案是未知的。

       任何软件能取得的最快速度是作为运行在硬件上的命令或限制。无论如何,硬件靠自己来提升性能不是那么容易的。他让软件指挥(direct)硬件来有效的工作。“指挥(direct)”翻译成管理成本(management cost),我们通常不喜欢为不必要的东西买单。因此,用软件来提升性能相对更好的硬件是一个很好的主意。这让我们花费较少的代价来取得更多的性能。

       管理成本(management cost)是影响代码的移植性和可维护性。代码的可维护性、易移植性这个话题不需要太多的解释对于那些每年有很多维护经费的开发者。

       CVM代码的可移植性、可维护性总是非常重要的。现在phoneMe开源了,每个人都能够贡献bug 修改(fixes)和增加(enhancement)。我想提醒大家代码需要坚持维护,这对每个人都非常重要。如果开源的一段代码没一个人能读懂是怎么一回事??这可能用密匙来加密。我说丢弃因为代码恢复往往是恢复甚至是有时作者已经通过。(完全翻不通:After all, how open-source is a piece of code if no one can understand it? It might as well have been encrypted with the secret key thrown away. I say thrown away because unmaintainable code also tend to be unmaintainable even to its creators once some time has passed.

       关键点是我们不需要牺牲可维护性在性能上。这是一个谬论,当我们不能在同一时间拥有高性能和可维护的代码。非常难得的是有异常在我们可维护的代码中。无论怎样,我们应该包含损害不至于通过基础代码影响本地化。(The point is that we shouldn’t sacrifice maintainability in the name of performance. It is a fallacy to think that we can’t have both high performance and maintainable code at the same time. Very rarely will there be an exception where we might have to sacrifice some maintainability. Even then, we should try to contain the damage so that it is localized and not wide spread across the code base.

       这是CVM的解释程序循环用C写而不是汇编的一个原因。这的确可以用汇编来写,但代价是巨大的,在你移植和维护的时候。在实践中,我们发现CVMC写的解释程序循环的效率和优化的汇编循环差别不大,但移植性和可维护性很好。

结束

谢谢大家听我讲了这么多,下次将介绍CVM的世界,子系统,数据结构,还有一张大图片

祝有美好的一天!

2007年02月08日

    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.
原文URL:http://weblogs.java.net/blog/mlam/archive/2006/11/when_does_javas_1.html#more

 在我关于性能的文章中有一个评论,问到:我想到关于设备日益强大的处理能力和更多的RAM的事实,我想什么时候才是JavaSE代替JavaME/CDC的最好选择?你需要多少的CPU,RAM,Cache?
 在我回答这个之前,我必须放弃一个我是一个工程师的,这对我的雇员,Sun,甚至是Sun的其他工程师是不必要的。基于这点,让我们进入这个问题吧。。。
JavaSE or JavaME
 最初的问题是基于设备兼容性和性能为依据的。这个问题有许多尺度比我们第一次看到的。这个问题基于规范,普遍存在的,可移植的,等等。让我们从显而易见的地方开始。。。
设备能力
 如果你的设备象一个桌面/服务器(象奔腾系列)建立在嵌入式的盒子里。那么,常规来看,JavaSE VM能够取得更好的性能。如果性能稍微差些,那么你的路就长了。我说奔腾系列仅仅是给你一个计算能力的比较。我不是说只有X86处理器。显而易见,JavaSE也为SPARC和PowerPC移植,他们有同样的规则应用在这里。
 设备性能不只是关于选择处理器。以PowerPC为例。它嵌入我们已知的许多桌面和服务版本。处理器核心是一样的(执行同样的代码),但他们的性能是不一样的。显而易见他们的时钟频率,高速缓存是不一样的。还有其他的硬件不同(例如主板等级)在性能上。高速缓存体系,2极缓存,主内存容量,内存速度,总线速度,内存和I/O体系,I/O处理器,MMU,DMA,TLAB size,辅助存储器(硬盘,闪存)等等。。。你的设备有这些特性的大部分,这样看起来JavaSE更好,反之亦然。
 仅仅是看下内存容量,我想JavaSE在10,100MBs,甚至GBs内存上典型的操作有同样的footprint。CVM的操作通常在10MBs以下。当然,这取决于你的应用程序在做什么(对JavaSE和CVM一样)。但这些数字给你一个想法。如果你的设备有16MB RAM,CVM可能是你最好的办法。如果你有32MB RAM,它只会得到一点的提升,这依靠你努力达到的程度。CVM对大多数嵌入式应用程序仍然是最好的办法。低于100MBs,它仍然是比JavaSE有希望的。如果设备有1G或更多,我非常同意JavaSE是更好的选择。
 对于高速缓存,这更难告诉我们。0~10 KBs于CVM匹配。10~100 KBs,仍然是CVM。如果高速缓存大于1MBs,你可以默认运行JavaSE。但这不意味这CVM不是一个更好的选择。
 对时钟频率,低于100MHz,CVM是你较好的选择。100MHzs~1GHz,仍然是CVM。超过1GHz,JavaSE是当然的选择。但Sun在不久前展示了这些,CPU性能不只是时钟频率(参考 CoolThreads)。因此,上面的数字相对的。事实上,上面我提到的所有方面只是基于我的经验的猜测教育。我们可以使用这些提示,但真实世界的案例是不同的,这就是上面给出的案例没有固定规则的原因。
 现在,我们来谈论这些显而易见的材料,让我们进入那些人们没有想到的。。。
嵌入式系统开发者注意
 嵌入式硬件常常需要下面的特性:便宜(成本方式,非品质),可靠的,可能滞后于技术发展水平曲线一点,不会太多。它需要便宜是因为目标消费者是平常的人。便宜意味着你能够卖的更多。可靠性是显而易见的,没有人想要一个不可靠的系统。另外,死机的蓝屏(从其他的计算机体验)接受的体验。在嵌入式设备,如果设备失败,人们会得到真的伤害或苦恼。基于这些,设备厂商小笔记簿会受到伤害。消费者期望系统没有异常的工作。The lagging(绝缘材料?)的曲线部分是大部分cost reason(成本?)。曲线的最新的边缘是非常昂贵的。太多的lagging是昂贵的,因为部分(它们的替代)很难找到。在曲线之后的痕迹有一点典型的收益。显而易见,它们不能应用于所有的嵌入式设备。但我的焦点在这个观点,异常的案例也会背离这个这个理由。
 因此,这对于嵌入式软件(包括JavaME)开发者意味着什么?
可移植性
基于技术曲线变化,硬件设备应注意升级老设备导致成本过高的。当然,升级也会带来对硬件性能有利的地方。有时候,升级不仅仅是替换CPU,它是全部体系的更换。
有时候,厂商会基于不同的性能提出不同的硬件等级。比如,低端和高端是2个不同的市场,这些设备不会总是建立在同样的CPU体系上。
这是为什么JavaME吸引人的地方(write once run anywhere)。但WORA依靠Java VM的移植。因此,VM的可以移植性是非常重要的。在这之后,开发成本和移植成果同样棘手。 它会使厂商想把这些都最小化。这也是CVM更适合的一个原因。我会在一系列文章中解释,CVM的体系对不同的平台来说更容易移植。这不是说JavaSE不能移植,只是它不太容易做到。我在前面已经指出,可以移植性来自于性能成本。尽管这些成本对大部分案例来说微不足道。这就是为什么CVM的性能在好的设备上比不上JavaSE的原因。相反的,JavaSE难以移植。CVM选择可移植性,JavaSE选性能。
普遍存在的
 普遍存在的,我没有提及那些支持java的设备作为在这个领域里使用它们的小组。在这个案例里,我会提及VM移植的实用性。这些都基于易于移植。因为CVM是容易移植的,它的移植可用性(CPU体系和OS平台)多于JavaSE。举个例子,像这写的,CVM移植到ARM,MIPS,x86,SPARC和PowerPC。一些消费者会把CVM移植到其他CPU体系。前面我们知道,JavaSE只移植到x86,SPARC和PowerPC。你选择一个源代码已经移植的可用VM。除非你自己想做移植。这是一个可选项(通常这个工程师的玩笑,因为你的工作多移植有影响),你必须花时间去将它做正确。因此,这需要依靠你的开发者的时间表。如果你选择作一个移植,CVM是一个值得关注的容易移植的选择,对于你想在短时间里得到正确的高可移植性。
 这是CVM体系对于多样的嵌入式系统便于移植的一个因素,这是必要的折衷。
符合目的
像上面说的,你需要基于你要怎样的java平台特性在你的设备上运行来决定。让我们看几个例子:
案例1:你的设备是x86的盒子,只制造很少的数量(10s~100s),你可以在它身上花大量的金钱。它有1G的内存,有硬盘存储器。它主要运行一个java程序。这个程序有很沉重的线程负担,高性能。这个程序有特殊的目的,只对于这个盒子。这个盒子不被期望运行其他程序,尽管你可以升级它来适应你的程序。那么你的选择是JavaSE。关键字是:桌面/服务器类硬件,高性能,特殊目的的软件。它不服从标准。
案例2:你的设备要卖上百万台。它运行在ARM处理器。32M RAM。只有非常少的高速缓存。基于JavaME CDC1.1它有自己的组件,有自己的规范,应用在一系列的市场。那你的选择是CVM。关键字是:便宜的设备,小或低的性能,需要适应CDC。
案例3:你的设备是服务器,需要运行JavaEE servlets,它要在同一时间提供1000个servlet。每个servlet需要运行它自己的进程,不是线程。Servlet自己有多个线程。Servlets被期望有短的生命期。性能是临界的。每个servlet操作一个来自网络的服务会话。服务是未知的(没有图形),不计算范围(没有太多的问题需要解决)。如果servlets没有及时响应,,使用设备的用户会苦恼,非常的苦恼。设备有4G内存,配置4个超级CPU,需要适应JavaEE。
显而易见的答案是。。。JavaSE?不绝对。这是为什么:JavaSE比CVM有大的footprint。JavaSE基本的footprint是20MB。CVM的基本footprint在2MB以下。每个servlet需要4MB的内存来运行。因此,运行servlet的一个JavaSE实例需要大约24MB的内存。每个运行servlet的CVM需要大约6MB。
如果设备任何时候都有4GB内存,它可以运行170个JavaSE实例,或者682个CVM实例。如果你不想处理硬盘内存分页,那么你想同一时间你可以响应1000个服务请求里的多少?
我不想回答这个。答案是你必须清楚你想的。显而易见,仅仅是考虑设备性能,你不能做出最好的决定。其他方面,需要解决适应JavaEE。CVM完全适应Java VM第2版规范。因此,你可以在它上面运行JavaSE和JavaEE。但不会增加CVM的footprint,它仅仅是2MB。是的,我没有说出JavaSE VM的footprint的具体数量。我的例子的人为的,但它不是一个不可用的。它解释了我观点,事情不总是像它看上去的那样。有许多的事情需要考虑,的确,硬件/设备性能是不最重要的因素。
这对你意味着什么
 好的工程实践总是你决定的指南。明白你需要的(你的程序要干什么),考虑所有的因素:设备性能,可移植性,普遍存在,适应性,还要其他我们在上面谈到的。

祝有美好的一天!

2007年01月30日

         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.
原文URL:http://weblogs.java.net/blog/mlam/archive/2006/11/performance_how.html#more

性能:太多的好事?
               这篇文章需要开发者有PhoneMe Advanced VM和J2ME方面比较深奥的知识。
              如果你已经看过了phoneMe Advanced VM的代码,你可能已经注意到许多函数和结构都是以cvm为前缀的。CVM是sun公司CDC VM的名称,而cvm作为前缀是VM代码(特别是全局函数和结构)的标准习惯。这是所有使用sun公司CDC技术工作过的人的普遍常识,但在这里我还是要强调一下。另外,我简单的以CVM来代替phoneMe Advanced VM。
现在让我们开始今天的主题。。。
性能
           通常,没有用户会抱怨,当他们的软件有很好的性能。无论如何,性能伴随着价格。通常,这意味着复杂的代码和更好的硬件。这还意味着需要更多的内存来运行软件。对JavaME VM来说,它的目标是资源受限的嵌入式设备。这是一个明确的关系。因此,一些性能上的工作需要合理的代价。这意味这平台开发者不能做所有他们知道的书本上的优化方法。
          这样说,我希望你知道我没有说这些是因为CVM的性能是窘迫的。直到我们吃的,CVM是这个领域最快的虚拟机,如果不是最快的,给你一个关于CVM性能的想法。几年前,我们把CVM同JavaSE1.3的基于SPEC JVM98子集的client VM进行了比较。我们使用子集是因为SPEC JVM98使用了已经从CDC移出的不受赞成的API。因此,我们做了一些内部“移植”对这个比较。这个比较在PowerPC,PowerMac和Solaris SPARC机器上进行。同JavaSE比,CVM显示了80%~90%的性能,只有10%的静态footprint。你应该知道这是老的数据。JavaSE已经有了很大的提高,CVM也是。注意:我仅仅是共享这些比较而给你一个JavaME能达到的性能等级的概念,我没有说任何关于哪一个VM更好。
         因此,当我们谈论性能的时候,人们首先谈论的VM的一个组成是动态适应编译。而作为JIT,下面我会围绕比较谈关于一些性能的事。我会接触其他非JIT相关但又重要的领域或主题
静态编译 VS 运行时编译
           一个最大的错误是,工程师会在JIT开始执行从其他编译课本来的优化而不做太多考虑。这些优化技术中的一些是意味深长的,而一些不是。有一点,课本经常教的是静态编译。JIT,在其他方面,是动态编译。这是JavaME的资源受限请求的要素。你可以在它们的属性中找到对比。下面是2中技术的比较:
Static compiler                                                                       JavaME JIT compiler
能提供少量内存给编译工作                                                   必须最小化或限制使用的工作内存
能够提供更多的时间和CPU周期给编译工作                      必须最小化或限制CPU的消耗
假设所有的方法在编译时都是可用的                                   只能工作与一个子集
编译所有的方法                                                                       只编译正在使用的方法。
必须编译所有类型的代码                                                   只需用编译通用的类型,让解释器处理那些不常见的实例
             注意,很多静态编译都假定更多内存和CPU资源的可用性。因此,他们的编译技术有很多相似的假定。明显的,这些技术的一部分不适用于JavaME。
                注意,尽管java平台是一个动态环境,可以期望部分代码在运行时下载。后期绑定是Java VM和语言的特点。 这不匹配静态编译的假定:整个应用程序代码必须在编译期可用。
JIT的能力让解释器处理不常用的案例的执行,同样也减少非紧急代码在资源上的消耗(在编译代码和编译footprint)
             批评家会说在我上面基于广泛性的表格里的需求,在今天的编译技术水平下不是真的。为什么,对的,我同意。我假设静态编译也来源静态链接。但记住,你的编译课本可以没有包括这2个中的任一个。我也使用静态编译的严格定义。例如:我认为它是编译静态代码。
真实世界的执行会加入一些能力,包括动态代码(可以下载),但这些不是严格的静态编译。这里的要点是你不必盲目的应用一流的编译技术。这些技术常常对不同的系统(这对类似于java的平台)做目标和最优化,因此他们可能不适合这里。
            人们可能有这样的误解,通过静态编译的代码要运行的比实时编译的要快。这不是全对的,实时编译的代码能够超越静态编译的代码。一个关键的原因是,java平台是动态并且后期绑定的。我会在以后详细的讨论这个。
           因此,有很多的原因导致静态编译不适用,甚至当你关心的资源和性能受限大打折扣。
JavaSE Hotspot VS CVM
        因此我们不能从编译器课本挖掘出诀窍。那从JavaSE VM可以得到诀窍吗?回答是“可能”。首先,这是资源受限的问题。JavaSE已经和有许多资源的大系统匹配了。这完全是合理的和可接受的,他们使用这些资源给你最好的性能在你的话费下。但当这些资源在你的设备上不可用时,这些技术可能就不能用了。
            另一点对普通开发者来说不是那些显而易见,JavaME 执行(像CVM)不仅仅是JavaSE的小版本。JavaSE目标设备的类型和JavaME有很大差别。CVM不是因为比JavaSE Hotspot少了一些功能而变小,CVM体系被设计的它可以工作在不同的嵌入式设备上。在它设计上的每一级,不同的选择会导致速度-空间的权衡。由于这个原因,用在JavaSE里的技术可能不能应用在CVM里,因为它们有不同的权衡。
                 给你一些例子来说明JavaME设备和JavaSE的不同,一段时候前,我的一个JavaSE方面的同事发现了当他应用一个可靠的技术来改善高速缓存位置,基于一个基准他能得到20%性能的提升。这抓住了我的注意。20%是不容忽视的,因此,我应用这个技术到CVM。令我吃惊的是,基于同样的基准,性能下降了70%,发生了什么?不同的是,JavaSE运行在一个有大量高速缓存(上百KB甚至是几兆)的服务器上。而我运行在只有32KB高速缓存的基于ARM处理器的设备上。这个改良的高速缓存位置在这儿制造了一个冲突。
 这个有效提升JavaSE的技术相对CVM来说,这也不是一个精确的例子。在这个例子里,他可以解决。但如果最优化代码,这项JavaSE技术可用于那些有着巨大缓存的设备?把这个技术应用在CVM实际上会造成那些没有大量高速缓存的JavaME设备性能的大幅下降。
              因此,关键是我们不要盲目的应用从JavaSE来的任何重要技术。注意上面的例子也说明了JavaSE VM运行的不比CVM快,当它运行在JavaME设备上时,即便你有大量的RAM(不是系统高速缓存)。这就像你想要你的汽车使用火箭引擎而有更大的马力。这听起来是一个不错的主意,但你的燃料系统不能支持它。结果也不比一般的汽车快。什么也没有改变。
              JavaME不仅仅是JavaSE的缩小版。对比JavaSE和JavaME就像对比苹果和橙子。
 基准
                 最后,确定优化是否工作的最好方法就是实验。 我们经常自己做这些事情。当一个念头失败,我们不混合基本代码。检测优化是否联合的一个重要的标准是,花费了多少成本在小组的资源分配上。另外,它带给了你多少性能的提升。
                 测量性能的提升,你需要运行某个类别的基准。一个常见的错误是人们运行了一个小的基准仅仅在测试一个优化提升的范围。真实世界的应用程序可能不只参加了一个循环和一个代码范围。因此,基于真实世界应用程序的基准是一个可靠的指示。对JavaMe来说,我们喜欢SPEC JVM98,但像前面说的,它不能不修改那些不赞成的方法而运行在CDC上。另外我们还喜欢EEMBC的GrinderBench。
               如果可能,运行你的基准在JavaME类型的设备上。就像上面指出的,JavaME不同于JavaSE,你改变在JavaSE类型的桌面或服务机器的的基准将给你指示,改变的结果是否有增长。但不同的JavaME设备,你得到的结果不完全一样,需要有适当的工程判断。
另外的性能
                一般的,java平台的性能不仅仅指执行引擎(解释器,JIT)。VM运行时和类库的品质也是游戏的一大部分。有些时候,它们甚至比VM重要。这包括主要依靠本地代码(相对于java代码)的graphics/GUI应用程序。无论如何,这不是意味着他们不依靠VM来运行。严格来说,VM运行时库是VM实现的一部分,我们也需要优化它。
                VM也能提供帮助机制使类库能执行的更好。他们需要共同工作。
               最后,有一件关于本地代码的事情。一些人认为他们的代码会执行的更快如果他们主要代码都是本地代码。这确实是个谬论,因为一系列的原因,使用本地代码确实会造成性能的下降比起一些功能使用java字节码。
               所有的这些都会在后面讨论。
这对你意味着什么
           性能是个复杂的主题。我们在这里仅仅是触及到了它的表面。正如我上面想要指出的,事情不是总想它们看起来的样子。当试着对JavaME系统执行性能提高的工作,对于嵌入式系统开发者来说,谨慎的思考是必要的。每一种优化技术需要在这个地方个别的进行评估。
            祝有美好的一天!:)

2007年01月25日

         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.

 原文URL:http://weblogs.java.net/blog/mlam/archive/2006/11/introduction_to_1.html

           如果你现在在阅读这篇文章,那么你对Sun通过PhoneMe工程来开放J2ME源代码有所了解,如果不是,点这里了解关于PhoneME。
一些背景信息
       开放我们代码的目的是允许你能够访问它,学习它,并有可能贡献你自己的改进。现在你已经可以访问一个代码链了,但访问和能够理解以及贡献有很大区别,它需要一些特别的知识。就大部分来说,只有Sun的雇员和少数例外(使用我们技术的公司的一些核心虚拟机工程师)能够这样做。我提到的知识包括:代码规范、术语表、设计原理、代码组织和设计权衡等等。但我相信你有足够的智慧来及时的领会这些,但我想这不是你在这个项目里开始的地方。
因此,我会就这些主题写一系列的文章(从这篇开始)来使我们的交流变的更容易,使每个人都能得到很到的资料而不用浪费时间在平常的事情上。我也会写一些像“怎样确定子系统的工作”之类的技术文章当我有灵感的时候。我允许你留下注释来告诉我你想我讨论的主题,或是你想我阐述明白的事情。在我选择文章主题的时候会考虑这些。
       在我开始前,你会问我如果来共享这些知识(为什么你要相信我的确知道我在谈论什么)。因此,关于我:工作在PhoeMe Advanced项目的虚拟机小组,负责建立和维护虚拟机。我从CDC1.0发布前就在这个小组工作。因此我和这些代码已经工作了很长的时间。在我们小组工作的过程中,我们不会正式的把虚拟机按我们工作的分成部分。我们基本会在任何需要的地方做必须的工作。因此,每一个虚拟机工程师的代码知识都是全面的。当然,我们每个人都有比其他人熟悉的领域。我需要指出我是一个虚拟机工程师(相对于类库工程师),我的专业技能集中在虚拟机和一些核心系统代码上。我也有一般的标准类库知识,但和他们比我不是专家。我们每个工程师都有自己的领域。因此我会写关于PhoneMe Advanced虚拟机的文章,因为这是我擅长的领域。
好的,让我们开始我们的第一个主题。
The meat(不知道怎么翻译了)
    前面,我说过深奥的知识我们会很少涉及,甚至在使用我们技术的用户里面。原因是这些用户很少有需要和动机去修改我们称作 共享代码 的部分。但现在这不会维持太久了。有太多的创新和特点需要在我们的代码里实现,这部分历史只限于Sun的雇员。我们的用户,在其他方面,需要关注的是我们称为 HPI (Host Porting Interface)的代码部分。点这里察看CDC移植指南,它会告诉你 HPI 的细节。实际上,在代码里有 HPI 文档如果你知道在哪能看到的话。你也可以从其他网页上找到一些有趣的文档。
设计原理(Design Philosophy)
       虚拟机被设计成高移植性,尽可能的减小代码以用于尽可能多的平台。这是虚拟机的基本规则。
    在共享机制下保证尽可能多的普通代码被减小。只有依靠硬件和OS的代码在共享代码以外。这些和硬件和OS捆定的指那些 目标或平台特殊的代码。点这里察看PhoneME Advanced src 列表。这有几个特殊的例子, arm ,linux ,linux-arm 文件夹。Linux 目录包含那些所有在linux移植下用到的代码。这个通常有一些 HPI 执行被调用,从 share 目录下。
     Linux-arm目录包含附加的用户定制的全部或重载的执行在linux目录。这些用户定制只能对于linux arm 移植。
     arm 目录包含针对arm的特殊代码,通常,他们看起来是一些有效函数(有些情况它们是汇编代码)。它们能被不同的arm平台移植调用。
     你在OS文件夹(如linux)下可以找到代码同等的共享文件夹(包括它们的子目录),紧随其后的是OS-CUP(例如: linux-arm)和CPU(如 arm)。这些事实表明了VM是多么的可以移植。移植成果通常只需要执行活修改目标特定的文件(这只是总代码里面的很小一部分)。
    我们工作的多数和共享代码里的创新对于目标特定代码只支持我们的决心来对所有移植平台最优化性能是相反的。这样,大部分性能工作都在共享代码里,这对每个移植都有好处。这样我们希望应用的特殊移植都被最优化。为了做到这点,我们常常把它们放在适当的OS-CPU或CPU文件夹下。
        我们代码组织的另一方面是代码维护以便于阅读。你会发现共享代码没有用 #ifdefs来定制不同的OS和CPU体系。你可以看到#ifdefs被enabling/disabling VM特性代替。你也可以看到OS,CPU和OS-CPU文件仍然易读,因为他们没有被#ifdefs用来和其他体系分开。
       在src文件夹,你可以看到portlibs文件夹。Portlibs被用来保存那些对多个移植来说通用的,但又不完全能以OS或CPU来区分的代码。一个例子就是工具链(gcc)或库/标准(posix,ansi)。多种的移植(OS和OS-CPU文件)可以选择使用portlibs里适当的代码。
       下面一个需要立即的概念是代码组织:根据OO术语,VM有一个父类。这个父类在共享代码里是明确的。每一个特定OS和CPU目标移植的VM是它的子类,通过继承来实现重用。OS代码是共享代码的直接子类。OS-CPU代码是OS代码的子类。CPU文件夹下的代码是可以被OS-CPU类选择使用的有用库。Portlibs 代码是OS-CPU类可以选用的另外的库
     总的来说,单个VM对一个给的的平台(OS和CPU)是incamated(这个词不知道什么意思)。无论如何,这是一个来自OS-CPU VM类的示例,它扩展了OS VM类,OS类又扩展了共享VM类。OS-CPU VM类也可以重用那些在CPU和portlibs里被授权的类。
      留意,这只是代码组织上的一个概念模型。你会发现在代码里的父和子VM没有参考。这个概念模型表明看来也不完美。你可以发现一些代码关联方面的范围不适合这个提取。是的,这是一个例外,但这个模型是一个普通规则。
这对你意味着什么
    当你计划寻找那些完成某些功能的代码,想想这些功能的属于(shared,OS,OS-CPU,or in the CPU or portlibs libraries)。这可以帮助你快速的定位你感兴趣的代码。
    你也可以对你想贡献的代码做同样的思考。这个代码组织是VM完成的一个关键因素,它便于移植(这是对移动和嵌入式领域的VM是非常重要的特点)。代码回顾过程对代码贡献也是必须考虑到的。
    正如我前面提及的,不遵守这些协定你可以得到一些例外。请不要使用那些偏离协定的借口。作为代替,这些已存在的异常会被相应的修正(如果可能)。或者有一个很好的技术理由,这些情况不适合希望的模型。如果理由充分这些异常是被允许的。
嗨,等一下
 所有那些关于可移植的部分听起来都是很好的,但是不是所有的这些层都在性能上有效果?回答是否定的!“不”是对那些我们担心的例子。是不是VM作为高执行蚂蚁它可以内联到任何地方,摆脱压条?可能不会,但我们在可移植和易维护中有一个折中。注意,在实践中,一些执行是可以被忽略的。这就是说,代码不被执行在本地路径。层使用了多样的技术来被执行(那些我在这不会深入)并防止不必要的执行降低与它有关的损失。当然,小组有一些度量来确保这些代码是有竞争力的。
 让我来说下其他问题:如果我们不试着挤出每一点可能的性能(因为我们必须权衡我们的设计原理),那么有多少性能是足够的呢?我会在下篇文章回答这个问题。
 祝有美好的一天!:)

            The article translate from Darryl Mocek’s blog. Darryl Mocek is a staff engineer for sun microsystems,Inc. currently working on J2ME CDC and related techonlogies. the URL is : http://weblogs.java.net/blog/darryl_m/archive/2006/12/phoneme_advance_1.html

           辛辛苦苦打了快一个小时的字,都写完了,结果Maxthon死机了,没上传气死了,不写了!

2007年01月23日

           The article translate from Darryl Mocek’s blog. Darryl Mocek is a staff engineer for sun microsystems,Inc. currently working on J2ME CDC and related techonlogies. the URL is : http://weblogs.java.net/blog/darryl_m/archive/2006/11/phoneme_advance.html

So, let’s begging…

      在这篇文章里,我们将会谈到 PhoneMe 高级工程的目录结构。当你读到这篇文章的时候可以通过这里的链接浏览源代码。你也可以加入这个工程来做这个。

      PhoneME Advanced的源代码从 components/cdc/trunk 目录开始。在 trunk 目录下有下面的目录:

  • build  包含 make 文件
  • src   包含源代码
  • test  包含测试文件

这是显而易见的。

      一旦你在 src 目录,你就在所有平台源代码的根路径上了。你可以注意到有一个 share 目录包含有一系列的硬件和操作系统目录。我们将在后面谈到它。这些目录包含了特定硬件和操作系统的源代码(除了 share 目录)。下面的 share 目录里包含了大多数的 PhoneMe Advanced 的代码

  • appmanager  包含应用程序管理代码
  • basis   包含个人基本描述的代码
  • cdc    包含个人连接设备的演示代码
  • classes    包含个人连接设备的代码
  • foundation   包含基础描述的代码
  • javavm     包含 cvm 的代码
  • lib         包含java 安全文件
  • native    包含共享的本地代码
  • personal   包含个人描述的代码
  • tools        包含工具

        这些所有的目录都可以有一个类别:基层的,个人的。在这里最典型的是 classes 和 native 目录。classes 目录包含了所有的java代码,native目录包含了所有的本地支持代码。native目录豪华 JNI 代码和那些需要用到的库,比如 Qt

        appmanager 目录完全由java代码组成,没有包含本地代码。 cdc 目录只包含了演示代码,lib 目录包含了2个 text 文件来描述和java 安全有关的东西,一个策略文件和一个安全文件。tools 目录包含java代码和本地代码,但现在的目录结构有稍微的不同,现在我集中在 profile 代码。

       classes目录下面,大部分是 java package的根。所以你在下面能找到 java/lang 目录。

        CDC profile 建立在其他部分之上。所以 personal basis profile 包含了基础profile,personal profile 又包含了personal basis profile。 在这些目录中,foundation profile 代码包含在 foundation 目录。personal basis profile 包含在 personal basis目录中。在这些不同的目录中你可以找到一些相同命名的profile文件。比如:你可以在 basis/classes/common/java/awt/toolkit 下找到 toolkit.java 。在 personal/classes/common/java/awt/toolkit 下找到 toolkit.java 。这说明他们是不同系列的 profile 文件。

          在 build 目录下,你可以找到和 src 类似的目录结构。一个 share 目录,它包含了跨越所有平台的 make 文件。特定的目录对应特定的平台,下面的 make 文件都是对对应平台优化了的。