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;                                       \
    }
複製代碼
相關文章
相關標籤/搜索