首页 > 代码库 > 一个简易JVM的实现

一个简易JVM的实现


http://catpad.net/michael/jvm/是一个开源的简易的JVM实现。它实现了大多数的JVM功能,实现简单,适合广大技术爱好者了解JVM的内部工作原理。可以参考《Inside the Java Virtual Machine》by Bill Venners,本书对jvm结构,Java class的定义有详细的讲解。

本文介绍下其中的一些关键实现点:
类加载,class loading
线程和同步, thead,synchronization
代码解释
GC
JNI


1)类加载流程
入口Result CONSTANT_Class_info::resolve()
流程:
调用ClassLoader的get_class:class_file->class_loader->get_class(resolved_class_name, utf8_info->length);
如果class已经被加载过:使用加载过的ClassFile。
如果class没有被加载过:调用ClassLoader的load_class加载class。每个新加在的class由ClassFile表示

ConstantPool
表示class文件的constant pool。ConstantPool构造需要ClassFile

Result CONSTANT_Class_info::resolve()
加载相应的class文件,得到对应的ClassFile对象。

ClassFile
表示一个被加载到内存的java class(从class文件加载到内存)。

ClassFile::make_bootable(filepath, boot_method, boot_instance);
创建一个bootstrap(类名是__boot_class__) class和一个main method,该方法的实现就是直接调用jvm启动参数中给出的class的main方法。

为什么需要一个手工创建一个bootstrap class?而不是直接调用用户提供的class main方法?
作者在介绍中给出了原因:
Special technique is used to "boot" the virtual machine. We want the call of the static "main" method to be transparent for the JVM, i.e. the "main" method should be called just as any other static method. During the method invocation the main class of the execution will be resolved just as any other class inside the JVM.


Result ClassFile::initialize()

执行class的静态初始化代码,即clinit,用来初始化static变量。


Result ClassFile::read(u1 *& stream_pointer) 
从文件流中读数据,根据class的格式定义解析。

ClassLoader
不支持字节码验证功能。
ClassLoader::find_class
查找class是否已经被加载过,如果没有被加载过,调用load_class加载。

ClassLoader::get_class
查找是否已经被加载过。

ClassLoader::define_class
创建这个class的ClassFile对象。如果该class不是Object,还会加载相应的接口和父类。
针对每个被加载的class,还会创建一个Class对象(object of Class),即Object.getClass()的返回值。

ClassLoader::load_class
加载class。先调用parent classloader,采用类加载的代理(delegate)模型。

2)线程和同步
线程没有采用os native thread,而是采用user space线程实现,采用基于优先级的轮询调度算法。

Thread
Thread表示一个Java线程。JVM中的每个线程由该class表示。

Thread::start
实现线程的初始化。输入参数是instance和线程执行方法。
内部流程:
分配Stack对象:stack = new Stack(this);
分配初始栈帧stack frame:stack->push_frame(current_code_attribute->max_locals, current_code_attribute->max_stack);
设置thread状态为runnable:set_running(); 该状态的线程可以被调度执行。

Thread::step()
从当前方法中取出下一条JVM指令并执行: (*opcodes[opcode].exec_function)(this);
如果执行过程中出现exception,调用treat_exception处理。
方法调用指令包括invokevirtual invokespecial invokestatic invokeinterface
在处理这些指令时,会在stack上分配新的栈帧,同时执行context_switch_up

Thread::context_switch_up
用来切换线程的上下文,如当前执行的class,PC等。

Stack
每个Thread包含一个Stack,每个Stack由若干个Frame构成,Java的每次方法调用都会在Stack上分配一个Frame,当方法返回时回收相应的Frame。每个Frame包括了局部变量(local variables)和操作数栈(operand stacks)。

push_frame:分配一个frame
pop_frame:回收当前frame

The operand stack of the calling frame will become the local variables of the frame being created。该方法用来优化参数传递,包括输入和返回。
push_overlapped_frame

ExecutionPool
保存JVM中的所有线程,并实现thread的调度。
Thread * ExecutionPool::resched()
运行线程调度:调度采用基于优先级的多个队列,每个队列中的线程采用轮询(round robin)算法。

锁lock
锁是用来实现互斥(mutual exclusion)的机制。在代码进入临界区(critical section)时,需要使用锁来保证互斥,即在任意时刻只能有一个线程访问临界区。
java中的锁通过synchronized关键字实现。synchronized通过JVM中的两条指令实现:monitorenter和monitorexit。在java.util.concurrent.locks中提供了Lock,也实现了锁的功能,Lock使用硬件处理器的CAS(compare-and-swap)指令实现,不在JVM中实现。

InstructionResult monitorenter(Thread * thread) opcodes.cpp
该函数用来实现monitorenter指令,申请锁,申请不到,阻塞当前线程。
实现方式:每个java对象(实例)都包含一个monitor对象。
void monitor::acquire(Thread * thread)
如果没有线程拥有当前该monitor,获得锁;否则将该线程状态改为Waiting

InstructionResult monitorexit(Thread * thread)
实现monitorexit,释放锁。
int monitor::release(Thread * thread)
释放当前锁。如果counter==0,从阻塞队列中选择一个线程,将锁的所有者改为选出的线程。

同步
同步(synchronization)用来协调/控制多个线程之间的运行顺序。java中的同步使用Object.wait和Object.notify实现。Object.wait和Object.notify会调用JVM中的代码来实现其功能。在java.util.concurrent.locks中提供了Condition,来提供同步功能,同样Condition的实现不在JVM中完成。

在使用wait,notify时,要求The current thread must own this object‘s monitor.
为什么有这个要求?因为在实现同步时,一般会涉及到共享资源(shared resource),要求每个线程都申请锁是为了防止对共享资源的并发操作。如生产者/消费者模型中的队列,就是共享资源。

Result Thread::wait(InstanceData * id)
该函数实现Object.wait,InstanceData 就是object。
每个对象有一个waiting_set,用来记录等待在该对象上的线程。
实现流程:
将当前线程加入waiting_set
释放拥有的锁,如果有等待在该锁的线程,选择一个运行。这个waiting_set和monitor.waiting_set不同:一个用来同步,一个用来互斥。
修改线程状态为Waiting

Result Thread::notify(InstanceData * id)
该函数实现object.notify
实现流程:
从waiting_set选择一个线程,
该线程需要重新申请锁,monitor::acquire(candidate)

Result Thread::notifyAll(InstanceData * id)
该函数实现object.notifyAll
遍历waiting_set,对每个线程执行申请锁操作。

3)代码解释
执行引擎(execution engine)
VirtualMachine是JVM执行引擎的主体代码,主体功能就是从class文件中取指令,执行。采用解释(inteperation)执行方式。
执行引擎的主体代码:
while (ture) {
    Thread * thread = execution_pool->resched();
    Result result = thread->step();
}

JVM指令表 opcodes.cpp
Opcode opcodes[]:JVM指令表,包括每条指令的解释程序。Thread中也包含了一些指令的解释程序。

4)GC
对象的表示
对象包括两种:一种是java中的对象,通过new创建,使用InstanceData表示;一种是class中的静态数据(static),每个class对应一个这样的对象,使用ClassData表示。

InstanceData
java中的每个对象在JVM中都使用一个InstanceData对象表示。
InstanceData::create()
创建instance。从ClassFile中读取类成员变量(实例变量),分配空间。class_file->instance_heap_manager->alloc(total_size, data_start);
对成员变量赋默认值

ClassFile::create_new_instance()
用来创建一个对象的实例(及new的实现)。

HandlePool
用来保存对象handle和地址的映射。内部采用hash方式。java中的对象引用使用的是handle,jvm内部操作会转为地址。
HandlePool::put_instance(InstanceData * id)
将对象加入handle pool,返回对象handle。

HandlePool中还保存当前JVM中的所有roots对象(MultiHashTable roots)。

GC会回收那些不是roots且没有被GC roots引用的对象。

HandlePool::put_root(InstanceData * root)
Called when a stack frame pushes a reference to its operand stack or stores a reference into its local variable
stack_frame::push_reference和stack_frame::store_reference会调用该函数。
HandlePool中的root用来记录当前所有栈帧中的root对象。每个root对象中会包含其他对象的引用。

HandlePool::remove_roots
Called when a stack frame is destroyed together with all its "roots".
该函数在Stack::pop_frame()被调用。

ArrayInstanceData
用来表示java中的array对象。

ClassData
ClassData::prepare()
读取类变量,分配空间,附初始值。该功能和InstanceData::create()类似,只是每个class只有一个ClassData,但每个class可以对应多个InstanceData。

HeapManager
管理内存空间的分配,ClassData和InstanceData的空间分配都通过HeapManager实现。

空间的回收 
垃圾回收运行在单独的低优先级线程中(garbage_collector_thread)。采用mark/sweep算法,不支持分代(generation)。代码入口GarbageCollector::step()
标记(mark)流程:
从当前的roots对象开始遍历,handle_pool->roots
对每个找到的对象执行mark算法:chunk->mark(run_counter);
对该对象所引用的其他对象执行mark算法:mark_reference_fields(id);

GC也会收集类对象,ClassData对象。
// Traverse all reachable class data memory chunks

回收(sweep)流程
HeapManager::sweep
找到所有mark_counter小于当前run_counter的内存块(chunk),回收。

5)JNI
实现方法是通过注册的方法,将native method注入到jvm中。
native代码处理入口:Thread::invoke_native_method
将线程状态改为Native。

NativeHandler
NativeHandler::run_method
执行native方法

NativeHandler::register_method
注册native方法,native代码实现通过该方法将方法注入jvm中。

void NativeHandler::init()
加载一些固定的native code

Thread的native code:
java_lang_Thread.cpp