本文在研究kaffe的基础上,吸收kaffe虚拟机的主要思想,用C语言作为开发语言,采用了及时编译器作为执行引擎,实现了一种Windows平台下的Java虚拟机。
3、垃圾收集
垃圾收集器主要的任务是检测出垃圾对象,然后回收垃圾对象使用的堆空间并还给程序。kaffe采用了增量垃圾收集的算法,而本实现中采用了三色标记并清除算法。
在标记之前先将堆中所有的分配单元置成白色,然后按深度优先算法遍历每一个单元。当垃圾收集器遍历一个分支的时候,如果一个分配单元及与之相关联的单元都被遍历到,则将其标记成黑色。 如果一个单元被遍历到,但是与之相关联的单元尚未被遍历,则将该单元标记成灰色。这时,垃圾收集器将继续遍历与该灰色单元相关联的单元,直到这些相关联的单元全部被遍历到,才能将这个灰色单元标记成黑色。最后当所有被遍历到的单元都被标记成黑色的时候, 将堆中被标记成白色的分配单元回收。
图4三色标记并清除算法的中间过程图
最后是对堆碎块进行压缩处理。是通过快速地移动对象来减少堆碎块。即把当前活动的对象移动到堆的一端,在此过程中,堆的另外一端出现一块大的连续的内存单元。所有被移动的对象的引用也被更新,指向新的内存单元。
4、线程同步
Java虚拟机中存在着以下两种线程:虚拟机系统线程和用户Java线程。虚拟机系统线程是指虚拟机运行过程中执行其特殊功能的线程,比如垃圾收集器线程等。用户Java线程是指用户编写的Java应用程序中明确表示要启动的线程,并且至少有一个Java线程,即main方法。
而Java语言的一大优势是支持多线程,这种支持主要表现在同步上。在java应用程序中使用synchronized关键字简单地使方法同步,而在Java虚拟机指令中则使用monitorenter和monitorexit指令显式地支持方法同步。Java虚拟机为每个对象都关联一个锁。当当前线程访问共享资源的时候,会执行monitorenter指令来弹出该对象引用,从而获取该对象引用相关联的锁。如果该对象已经被另一线程占用则当前线程就需要进入锁的等待队列,等待释放对象上的锁;已经获取共享资源的线程在释放资源的时候,执行monitorexit指令来弹出对象引用,并且释放与该资源相关联的锁,并让等待队列中的第一个线程获取该对象锁。
当然线程thread也可以根据需要对某对象obj多次上锁,上锁的次数放在计数器counter中。只有当counter为0的时候,即thread加在该对象上的锁被完全释放,其它线程才有机会使用 object。对象的数据结构为:
typedef struct Obj{ uint32 size; //堆中对象的大小 uint16 counter; //对象被上锁的数量 uint16 flag; //对象的状态标志 uint16 thread_id; //对该对象进行加锁的 //线程的ID } Obj; |
而在实际Java编程中, 程序员并不需要动手加锁,对象锁只是在Java虚拟机内部使用的。程序员只需要编写同步语句就可以标志一监视区域,当Java虚拟机运行程序的时候,每次进入一个监视区域,它每次都会给对象上锁。
5、线程调度
在本实现中,还需要考虑到在上述等待线程队列中如何选择下一个线程来执行,即线程调度问题。
哪个线程将获取notify命令,这一点在很大程度上取决于虚拟机的设计者,既可以通过使用FIFO队列来调度,也可以根据所有等待线程的优先级来调度,比如唤醒等待队列中优先级最高的线程获取刚刚释放的资源。而Bill Venners则从平台无关和执行效率这两个角度出发,提倡Java虚拟机的设计者应使用java.lang.Object类中的notifyAll()方法来代替notify()方法去唤醒等待队列中的线程。
处理好线程调度问题,就可以节省程序的执行时间,这对于提高Java虚拟机的执行性能是很有帮助的。
总结 本文在研究kaffe的基础上,实现了一虚拟机。并且对Java虚拟机中的关键技术及时编译器、垃圾收集器、线程同步和线程调度等做了分析。本文中所实现的及时编译器虽然在执行速度上比解释型的Java虚拟机快得多,但是不如自适应优化编译器,因为自适应优化编译器具有程序启动快,占用内存少的特点。如果要明显地提高虚拟机的性能,应该更多的从执行引擎着手。另外在Java应用程序的执行过程中许多时间是花费在多线程处理和垃圾收集上,如果在线程同步和线程调度上有所创新,也可以提高虚拟机的执行性能。
研究Java虚拟机的实现过程有重要的意义,程序员可以编写针对于不同平台下的裁减了的Java虚拟机,这样它就可以在实时嵌入式系统得到广泛地应用。相信Java虚拟机将在更多的领域得到不断的完善和发展。
查看本文来源