创建一个普通对象,类似执行A a=new A()
这条语句,通过反编译javap -c
可以得到对应指令如下
0: new #2 // class main/proxy/A
3: dup
4: invokespecial #3 // Method main/proxy/A."<init>":()V
复制代码
new/dup/invokespecial分别对应虚拟机的指令,后面跟随的#
表示常量池中的索引
- new:表示创建对象,注意执行完后对象并未创建完
- dup:赋值栈顶的值
- invokespecial:真正的执行实例初始化方法
对象创建完整过程在hotspot中的源码中可见 bytecodeInterpreter.cpp
对象新建
_new
当读取到_new
指令时,执行如下
CASE(_new): {
//获取常量池中的位置
u2 index = Bytes::get_Java_u2(pc+1);
//获取常量池
constantPoolOop constants = istate->method()->constants();
if (!constants->tag_at(index).is_unresolved_klass()) {
//常量池中已经加载了要新建的对象
...
UPDATE_PC_AND_TOS_AND_CONTINUE(3, 1);
...
}
//常量池中没有加载要新建的对象,执行加载流程
CALL_VM(InterpreterRuntime::_new(THREAD, METHOD->constants(), index),
handle_exception);
SET_STACK_OBJECT(THREAD->vm_result(), 0);
THREAD->set_vm_result(NULL);
UPDATE_PC_AND_TOS_AND_CONTINUE(3, 1);
}
复制代码
constantPoolOop
是个存放class常量的数组。class由class file规则定义。constantPoolOop中的大多数实例都是在class解析的时候就放入了
实例已经加载
确保对象所属类型已经经过初始化阶段
if ( ik->is_initialized() && ik->can_be_fastpath_allocated() ) {
...
}
复制代码
开始执行新建。
- 为新对象分配内存空间。
//获取对象的大小 size_t obj_size = ik->size_helper(); oop result = NULL; // 记录是否要将所有的字段置0值 bool need_zero = !ZeroTLAB; //是否在TLAB中分配对象 if (UseTLAB) { result = (oop) THREAD->tlab().allocate(obj_size); } if (result == NULL) { need_zero = true; // 直接在eden中分配空间,失败就重试,直到成功 retry: HeapWord* compare_to = *Universe::heap()->top_addr(); HeapWord* new_top = compare_to + obj_size; if (new_top <= *Universe::heap()->end_addr()) { if (Atomic::cmpxchg_ptr(new_top, Universe::heap()->top_addr(), compare_to) != compare_to) { goto retry; } result = (oop) compare_to; } } 复制代码
- 将分配到的内存空间都初始化为零
if (need_zero ) { HeapWord* to_zero = (HeapWord*) result + sizeof(oopDesc) / oopSize; obj_size -= sizeof(oopDesc) / oopSize; if (obj_size > 0 ) { memset(to_zero, 0, obj_size * HeapWordSize); } } 复制代码
- 设置对象头,根据是否要设置偏向锁,头部存在不同的设置
if (UseBiasedLocking) { result->set_mark(ik->prototype_header()); } else { result->set_mark(markOopDesc::prototype()); } 复制代码
- 继续执行下一条指令
尚未加载
它是由运行时开始执行新建
IRT_ENTRY(void, InterpreterRuntime::_new(JavaThread* thread, constantPoolOopDesc* pool, int index))
klassOop k_oop = pool->klass_at(index, CHECK);
instanceKlassHandle klass (THREAD, k_oop);
// 确保不是抽象函数
klass->check_valid_for_instantiation(true, CHECK);
//执行初始化,在instanceKlass的class中
klass->initialize(CHECK);
// At this point the class may not be fully initialized
// because of recursive initialization. If it is fully
// initialized & has_finalized is not set, we rewrite
// it into its fast version (Note: no locking is needed
// here since this is an atomic byte write and can be
// done more than once).
//
// Note: In case of classes with has_finalized we don't
// rewrite since that saves us an extra check in
// the fast version which then would call the
// slow version anyway (and do a call back into
// Java).
// If we have a breakpoint, then we don't rewrite
// because the _breakpoint bytecode would be lost.
oop obj = klass->allocate_instance(CHECK);
thread->set_vm_result(obj);
IRT_END
复制代码
- CHECK 属于宏定义,实际上表示的是线程
- instanceKlassHandle 属于宏定义,由
DEF_KLASS_HANDLE
定义,它重载了->
实际执行的方法就是instanceKlass
本身对应的方法
initialize的核心实现在initialize_impl
,在初始化之前首先要确保link
完成,如果没有则开始验证
bool instanceKlass::link_class_impl(
instanceKlassHandle this_oop, bool throw_verifyerror, TRAPS) {
...
//1.
instanceKlassHandle super(THREAD, this_oop->super());
if (super.not_null()) {
//执行父类的 link_class_impl
}
//2.
objArrayHandle interfaces (THREAD, this_oop->local_interfaces());
int num_interfaces = interfaces->length();
for (int index = 0; index < num_interfaces; index++) {
//执行每一个接口的link_class_impl
}
...
//3.
bool verify_ok = verify_code(this_oop, throw_verifyerror, THREAD);
...
//重写类的方法的所有字节码。这必须发生在验证之后,而且在类的第一个方法执行之前,同时只能执行一次
this_oop->rewrite_class(CHECK_false);
...
//4.
this_oop->relocate_and_link_methods(CHECK_false);
//5.
this_oop->set_init_state(linked);
...
}
复制代码
当没有执行link的时候,开始按照如下步骤执行
就是连接过程中的验证、准备、解析
- 在执行link当前class之前,先完成父类的link
- 在执行link当前class之前,先完成所有接口的link
- 此时当前类仍然没有link完,如果同时,代码的 rewrite 标志不是true,开始验证代码:大致过程为先以类为入口,一个个的遍历它的方法,然后读取方法的字节流,一个一个指令的去验证,比如
Bytecodes::_invokespecial :
指令。如果发现这个类没有加载过,则会执行加载对应字节码的流程 - 执行link。大致流程为将方法重写,并更新方法入口给编译器和解释器
- link执行完成
link的所有状态如下
enum ClassState { unparsable_by_gc = 0, // object is not yet parsable by gc. Value of _init_state at object allocation. allocated, // allocated (but not yet linked) loaded, // loaded and inserted in class hierarchy (but not linked yet) linked, // successfully linked/verified (but not initialized yet) being_initialized, // currently running class initializer fully_initialized, // initialized (successfull final state) initialization_error // error happened during initialization }; 复制代码
link完成之后开始执行真正的初始化
- 在要新建的对象上执行同步,当然得等到当前线程能过获取这把锁
- 如果发现要新建对象正在被其它线程处理,当前线程就会释放锁,并阻塞知道其它线程处理完
- 如果要新建对象正在处理的线程是自己,这代表发生了循环初始化,直接释放锁,并结束初始化
- 如果发现要新建的对象已经建完了,释放锁,并返回
- 如果初始化的时候,发现类的状态为
initialization_error
,释放锁,并抛出NoClassDefFoundError
- 否则记下C正在被当前线程处理中,类的状态为
being_initialized
,并释放锁。然后按照每个字段在ClassFile中出现的顺序,一个个的按照类的ConstantValue属性中的值初始化 新建类的 final static字段 - 如果要新建的类不是接口,并且它的父类还没有初始化,那么按照上面的所有流程来对父类做处理(在处理父类的过程中,一旦出现异常,新建类的状态就会标记为 error,此时会唤醒所有其他线程,并把这个异常抛出去)
- 查询新建类的class loader看是否启用了断言
- 执行新建类自己的初始化方法
- 如果自定义的初始化方法执行完成,那么获取锁,标记类已经完全初始化完毕,同时唤醒所有其他的线程,并释放锁,就此正常结束流程
- 没有正常完成,会创建一个
ExceptionInInitializerError
来包装扔出来的异常,如果由于OOM导致无法创建ExceptionInInitializerError,则会抛出OOM。在抛出去之前,获取锁,标记异常,唤醒所有其他的线程,并释放锁
自此 klass->initialize(CHECK);
执行完毕。开始在堆上分配内存
instanceOop instanceKlass::allocate_instance(TRAPS) {
assert(!oop_is_instanceMirror(), "wrong allocation path");
bool has_finalizer_flag = has_finalizer(); // Query before possible GC
//获取大小
int size = size_helper(); // Query before forming handle.
KlassHandle h_k(THREAD, as_klassOop());
instanceOop i;
//分配内存
i = (instanceOop)CollectedHeap::obj_allocate(h_k, size, CHECK_NULL);
if (has_finalizer_flag && !RegisterFinalizersAtInit) {
i = register_finalizer(i, CHECK_NULL);
}
return i;
}
复制代码
自此初始化结束
dup
执行如下
CASE(_dup): /* Duplicate the top item on the stack */
dup(topOfStack);
UPDATE_PC_AND_TOS_AND_CONTINUE(1, 1);
复制代码
本质上,就是拷贝
tos[Interpreter::expr_index_at(-to_offset)] =
(intptr_t)tos[Interpreter::expr_index_at(-from_offset)];
复制代码
invokespecial
关键部分如下
CASE(_invokespecial):
CASE(_invokestatic): {
u2 index = Bytes::get_native_u2(pc+1);
ConstantPoolCacheEntry* cache = cp->entry_at(index);
...
if ((Bytecodes::Code)opcode == Bytecodes::_invokespecial) {
CHECK_NULL(STACK_OBJECT(-(cache->parameter_size())));
}
//这里会找到对应的方法执行,f1对于不同的类型有不同的实现,对于 invokespecial指令来说,它就是 <init> 方法
callee = (methodOop) cache->f1();
...
//返回
UPDATE_PC_AND_RETURN(0);
复制代码
特殊方法:在java虚拟机中,所有的构造函数都拥有一个一样的特殊名字<init>
,它由编译器提供,由于名字本身是非法的,所以无法通过java语言来写,要去执行它只能通过JVM的指令invokespecial
,并且只会在没有初始化的实例上执行。
<cinit>对比<init>,<cinit>不是初始化方法,不会被JVM指令执行。同样的它也并不是一个合法的名字,名字本身由编译器提供,<cinit>的执行是属于初始化流程的一部分
- 返回类型是void
- 和其它构造函数一样,this引用会被编译器作为第一个参数插入
- 除了 Object 对象,它首先会执行另一个构造函数,如果是手动用了 this 是第一个,那么init就会先去执行同一个类的另一个 <init> 方法;如果没有使用 this,那么就会调用 super执行。(注意:同一个构造函数 this和super只能有一个,如果没有写他们的任何一个,编译器会自动插入一个无参数的 super构造函数。另外在super和this执行过程中的异常是不能被捕获的,如果能捕获,则完成后是一个初始化错误的对象,有风险)
- 当执行 init 到Object时,直接返回,然后依次的去执行实例变量的初始化
- 最后执行构造函数本身的实现
附bytecode代码布局
仅从对应的字节指令解析开始
获取到了指令之后,跳转到run开始执行解析
//883行
run:
...
//892行
while(1){
...
opcode = *pc
...
switch(opcode){
CASE(_new):{
...
UPDATE_PC_AND_TOS_AND_CONTINUE(3, 1);
}
...
}
...
do_continue: ;
}
复制代码
CASE(_new)
它本身是个宏定义
#undef CASE
#ifdef USELABELS
#define CASE(opcode) opc ## opcode
#define DEFAULT opc_default
#else
#define CASE(opcode) case Bytecodes:: opcode
#define DEFAULT default
#endif
复制代码
可以在Bytecodes.hpp中找到对应的指令
enum Code{
...
_new = 187, // 0xbb
_newarray = 188, // 0xbc
_anewarray = 189, // 0xbd
...
}
复制代码
UPDATE_PC_AND_TOS_AND_CONTINUE(3,1)
它是个宏定义,抽取部分如下
UPDATE_PC_AND_TOS_AND_CONTINUE(opsize, stack) { \
pc += opsize; opcode = *pc; MORE_STACK(stack); \
DO_UPDATE_INSTRUCTION_COUNT(opcode); \
DEBUGGER_SINGLE_STEP_NOTIFY(); \
goto do_continue; \
}
复制代码