hotspot虚拟机中java对象是如何建立

建立一个普通对象,相似执行A a=new A()这条语句,经过反编译javap -c能够获得对应指令以下html

0: new           #2 // class main/proxy/A
3: dup
4: invokespecial #3 // Method main/proxy/A."<init>":()V
复制代码

new/dup/invokespecial分别对应虚拟机的指令,后面跟随的#表示常量池中的索引java

  • new:表示建立对象,注意执行完后对象并未建立完
  • dup:赋值栈顶的值
  • invokespecial:真正的执行实例初始化方法

对象建立完整过程在hotspot中的源码中可见 bytecodeInterpreter.cppgit

对象新建

_new

当读取到_new指令时,执行以下github

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解析的时候就放入了数组

实例已经加载

确保对象所属类型已经通过初始化阶段bash

if ( ik->is_initialized() && ik->can_be_fastpath_allocated() ) {
    ...
}
复制代码

开始执行新建。oracle

  1. 为新对象分配内存空间。
    //获取对象的大小
    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;
          }
        }
    复制代码
  2. 将分配到的内存空间都初始化为零
    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);
        }
      }
    复制代码
  3. 设置对象头,根据是否要设置偏向锁,头部存在不一样的设置
    if (UseBiasedLocking) {
        result->set_mark(ik->prototype_header());
      } else {
        result->set_mark(markOopDesc::prototype());
      }
    复制代码
  4. 继续执行下一条指令

还没有加载

它是由运行时开始执行新建app

IRT_ENTRY(void, InterpreterRuntime::_new(JavaThread* thread, constantPoolOopDesc* pool, int index))
  //在常量池中找到对应的klass
  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完成,若是没有则开始验证jvm

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的时候,开始按照以下步骤执行函数

就是链接过程当中的验证、准备、解析

  1. 在执行link当前class以前,先完成父类的link
  2. 在执行link当前class以前,先完成全部接口的link
  3. 此时当前类仍然没有link完,若是同时,代码的 rewrite 标志不是true,开始验证代码:大体过程为先以类为入口,一个个的遍历它的方法,而后读取方法的字节流,一个一个指令的去验证,好比Bytecodes::_invokespecial :指令。若是发现这个类没有加载过,则会执行加载对应字节码的流程
  4. 执行link。大体流程为将方法重写,并更新方法入口给编译器和解释器
  5. 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完成以后开始执行真正的初始化

  1. 在要新建的对象上执行同步,固然得等到当前线程能过获取这把锁
  2. 若是发现要新建对象正在被其它线程处理,当前线程就会释放锁,并阻塞直到其它线程处理完
  3. 若是要新建对象正在处理的线程是本身,这表明发生了循环初始化,直接释放锁,并结束初始化
  4. 若是发现要新建的对象已经建完了,释放锁,并返回
  5. 若是初始化的时候,发现类的状态为 initialization_error,释放锁,并抛出NoClassDefFoundError
  6. 不然记下C正在被当前线程处理中,类的状态为being_initialized,并释放锁。而后按照每一个字段在ClassFile中出现的顺序,一个个的按照类的ConstantValue属性中的值初始化 新建类的 final static字段
  7. 若是要新建的类不是接口,而且它的父类尚未初始化,那么按照上面的全部流程来对父类作处理(在处理父类的过程当中,一旦出现异常,新建类的状态就会标记为 error,此时会唤醒全部其余线程,并把这个异常抛出去)
  8. 查询新建类的class loader看是否启用了断言
  9. 执行新建类本身的初始化方法
  10. 若是自定义的初始化方法执行完成,那么获取锁,标记类已经彻底初始化完毕,同时唤醒全部其余的线程,并释放锁,就此正常结束流程
  11. 没有正常完成,会建立一个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>的执行是属于初始化流程的一部分。

<cinit>是由编译器自动收集类中的全部变量的赋值动做和静态语句块中的语句合并产生的。固然这也意味着若是没有这些,在生成字节码的时候也能够不生成这些方法

<init>基本结构:

  • 返回类型是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;                                       \
    }
复制代码
相关文章
相关标签/搜索