JVM二:物件的建立(詳解new後發生的故事)
在Java中我們建立物件都會用new進行建立,下面我來接收一下new之後物件建立及記憶體分配的具體的過程
一:虛擬機器遇到一條new指令後,先去檢查這條指令引數是否能在常量池中定位到一個類的符號引用,並檢查這個符號引用代表的類是否已被載入、解析和初始化過,如果沒有,那必須先執行相應的類載入過程。
二:類載入檢查通過後,接下來虛擬機器為新生物件分配記憶體,因為物件所需記憶體的大小在類載入後是完全確定的(用引用,堆中存放例項),所以只需分配一個固定大小的記憶體即可。分配記憶體的方法用兩種(Java虛擬機器中)
①指標碰撞:假設Java堆中記憶體是絕對規整的,所有用過的記憶體都存放在一邊,空閒的記憶體存在在另一邊,中間放著一個以指標為分界點的指示器,分配記憶體就是把指標往空閒的那側移動物件需要的記憶體即可。但是如果Java記憶體堆不是完整的,就沒有辦法進行簡單的指標碰撞,
②空閒列表:空閒列表就是虛擬機器先對記憶體分塊,並用一個表記錄每個記憶體塊是否使用的情況,為物件分配記憶體時只需更新列表上的記錄即可,選擇哪一種分配方式取決於對中的記憶體使用情況是否完整。
但是在為物件分配記憶體的同時,需要考慮其它執行緒是否也在這一時刻需要分配,因此需要考慮到執行緒的安全問題,通過以下兩種方法進行解決:
①同步處理:一種是對分配記憶體的空間動作進行同步處理--實際上虛擬機器採用CAS配上失敗重試的方式保證更新操作的原子性
②本地執行緒分配快取TLAB:另一種是把記憶體分配的動作按照執行緒在不同的空間之中進行,及每個執行緒先預先分配一定的快取。
三:記憶體分配完成後,虛擬機器將分配到的記憶體空間都初始化為零值(不包括物件頭),如果採用的是TLAB的方法分配,則這一步驟在分配之前完成。
四:在上面的步驟完成後,從虛擬機器的視角,一個新的物件已經產生了,但是從Java程式的視角來看,物件建立方法才剛剛開始,還要執行物件的init方法(構造方法),一個物件才完成建立
以下是HotSpot直譯器的程式碼片段(C++)
// 確保常量池中存放的是已解釋的類 if (!constants->tag_at(index).is_unresolved_klass()) { // 斷言確保是klassOop和instanceKlassOop(這部分下一節介紹) oop entry = (klassOop) *constants->obj_at_addr(index); assert(entry->is_klass(), "Should be resolved klass"); klassOop k_entry = (klassOop) entry; assert(k_entry->klass_part()->oop_is_instance(), "Should be instanceKlass"); instanceKlass* ik = (instanceKlass*) k_entry->klass_part(); // 確保物件所屬型別已經經過初始化階段 if ( ik->is_initialized() && ik->can_be_fastpath_allocated() ) { // 取物件長度 size_t obj_size = ik->size_helper(); oop result = NULL; // 記錄是否需要將物件所有欄位置零值 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; // cmpxchg是x86中的CAS指令,這裡是一個C++方法,通過CAS方式分配空間,併發失敗的話,轉到retry中重試直至成功分配為止 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 (result != NULL) { // 如果需要,為物件初始化零值 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()); } result->set_klass_gap(0); result->set_klass(k_entry); // 將物件引用入棧,繼續執行下一條指令 SET_STACK_OBJECT(result, 0); UPDATE_PC_AND_TOS_AND_CONTINUE(3, 1); } } }