??我們了解虛擬機(jī)內(nèi)存劃分的人,都知道對象的內(nèi)存分配幾乎都是在堆上的,這一點(diǎn)在java虛擬機(jī)規(guī)范中的描述是:所有的對象實(shí)例以及數(shù)組都會在堆上分配(但是隨著JIT編譯器的發(fā)展與逃逸分析技術(shù)逐漸成熟俱饿,棧上分配歌粥,標(biāo)量替換優(yōu)化技術(shù)將會導(dǎo)致一些變化,擦扯遠(yuǎn)了拍埠,所以所有的對象實(shí)例都在堆上分配就不是那么絕對了)
下面說虛擬機(jī)中對象的創(chuàng)建幾個步驟
- 類加載檢查
??虛擬機(jī)遇到new指令時失驶,首先會去檢查這個指令的參數(shù)是否能在常量池中定位到一個類的符號引用,并且檢查這個符號引用代表的類是否被裝載枣购、解析和初始化過嬉探。如果沒有那必須先執(zhí)行相應(yīng)的類的加載過程。 - 為對象分配內(nèi)存
??當(dāng)類加載成功后棉圈,類的對象的大小是確定了的涩堤。對象內(nèi)存的劃分等同于在堆里劃分出一塊指定大小的內(nèi)存。
??假設(shè)內(nèi)存是規(guī)整的分瘾,所有用過的內(nèi)存放在一邊胎围,空閑的放在另一邊,中間分界點(diǎn)有個指針芹敌,那分配內(nèi)存就是把指針向空閑區(qū)域方向挪動一段于對象大小相等的長度痊远,這種分配方式叫做“指針碰撞”(Bump the Pointer)垮抗。
??如果內(nèi)存不是規(guī)整的氏捞,那么就需要一個表來記錄,記錄哪些內(nèi)存是占用的冒版,哪些是空閑的液茎,那分配內(nèi)存就是在表里找到一塊足夠大的空間分配給對象實(shí)例,并更新這個表的記錄辞嗡,這種分配方式叫做“空閑列表”(Free List)捆等。
??選擇哪種分配方式與堆是否規(guī)整決定,而java堆是否規(guī)整又和垃圾收集器是否有壓縮整理功能決定栋烤。因此,在使用Serial挺狰、ParNew等帶有Compact過程的收集器時明郭,采用的分配方式是指針碰撞,而使用CMS這種基于Mark-Sweep算法的收集器時丰泊,采用的是空閑列表薯定。
??除了如何劃分內(nèi)存之外還有一個是我們需要考慮的問題,因?yàn)閖ava堆是線程共享的瞳购,那么多個線程同時操作堆上的內(nèi)存就會有問題话侄,比如正在給a對象分配內(nèi)存還沒來得及移動指針(或者是沒有修改空閑列表的記錄),這時對象b使用原來的指針來分配內(nèi)存,就會產(chǎn)生問題年堆。解決這個問題有兩種方式吞杭,一種是對分配內(nèi)存的動作進(jìn)行同步處理(實(shí)際上虛擬機(jī)采用CAS配上錯誤重試來保證分配內(nèi)存的原子性);另一種是采用線程間不共享的內(nèi)存來分配嘀韧,每個線程預(yù)先在java堆中分配一塊內(nèi)存篇亭,成為本地線程分配緩沖(Thread Local Allocation Buffer,TLAB )。哪個線程分配內(nèi)存就在該線程的TLAB上分配锄贷,只有在TLAB用完需要分配新的TLAB時译蒂,才需要同步鎖定。注:虛擬機(jī)是否使用TLAB谊却,可以通過-XX:+/-UseTLAB參數(shù)來設(shè)定柔昼。 - 內(nèi)存初始化
??內(nèi)存分配完后,需要把將分配到的內(nèi)存空間初始化為零值(不包換對象頭)炎辨,如果使用TLAB這一過程也可以提前至TLAB分配時進(jìn)行捕透。這一步操作保證了對象實(shí)例字段在java代碼中可以不賦初值就使用,程序能訪問到這些字段的數(shù)據(jù)類型所對應(yīng)的零值碴萧。 - 對象頭必要設(shè)置
??接下來虛擬機(jī)要對對象進(jìn)行必要的設(shè)置乙嘀,比如該對象是哪個類的實(shí)例、如何才能找到類的元數(shù)據(jù)信息破喻、對象的hash碼虎谢、對象的gc分代年齡等信息。這些信息均放在對象的對象頭(Object Header)中曹质。根據(jù)虛擬機(jī)當(dāng)前的運(yùn)行狀態(tài)的不同婴噩,如是否啟用偏向鎖等,對象頭會有不同的設(shè)置
總結(jié)
以上步驟完成后羽德,從虛擬機(jī)的角度來看几莽,一個新對象誕生了,但從java程序角度看宅静,一切才剛剛開始——init方法還沒有執(zhí)行章蚣,所有字段的數(shù)據(jù)類型都是對應(yīng)的零值。所以一般來說執(zhí)行new指令后會接著執(zhí)行init方法姨夹,把對象按照程序猿的意愿進(jìn)行初始化纤垂,這樣一個真正可以使用的對象才算完成。
下面的代碼是HotPot虛擬機(jī)bytecodeInterpreter.cpp的代碼片段
u2 index = Bytes::get_Java_u2(pc+1);
ConstantPool* constants = istate->method()->constants();
// 確保常量池中是已經(jīng)解釋的類
if (!constants->tag_at(index).is_unresolved_klass()) {
// Make sure klass is initialized and doesn't have a finalizer
// 確保類已經(jīng)初始化
Klass* entry = constants->slot_at(index).get_klass();
assert(entry->is_klass(), "Should be resolved klass");
Klass* k_entry = (Klass*) entry;
assert(k_entry->oop_is_instance(), "Should be InstanceKlass");
InstanceKlass* ik = (InstanceKlass*) k_entry;
// 確保對象內(nèi)存已經(jīng)初始化
if ( ik->is_initialized() && ik->can_be_fastpath_allocated() ) {
// 取對象長度
size_t obj_size = ik->size_helper();
oop result = NULL;
// If the TLAB isn't pre-zeroed then we'll have to do it
// 如果TLAB沒有預(yù)先初始化那么need_zero為true后邊會進(jìn)行初始化
bool need_zero = !ZeroTLAB;
// 如果虛擬機(jī)啟用TLAB匀伏,那么在TLAB中分配對象
if (UseTLAB) {
result = (oop) THREAD->tlab().allocate(obj_size);
}
if (result == NULL) {
need_zero = true;
// Try allocate in shared eden
// 嘗試在eden中分配對象
retry:
HeapWord* compare_to = *Universe::heap()->top_addr();
HeapWord* new_top = compare_to + obj_size;
/*cmpxchg是x86中的CAS指令洒忧,這里是一個C++方法通過CAS方式分配空間,如果并發(fā)失敗够颠,轉(zhuǎn)到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) {
// Initialize object (if nonzero size and need) and then the header
// 如果需要熙侍,則為對象初始化零值
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);
}
}
// 根據(jù)是否啟用偏向鎖來設(shè)置對象頭信息
if (UseBiasedLocking) {
result->set_mark(ik->prototype_header());
} else {
result->set_mark(markOopDesc::prototype());
}
result->set_klass_gap(0);
result->set_klass(k_entry);
// 將對象引用入棧,繼續(xù)執(zhí)行下一條指令
SET_STACK_OBJECT(result, 0);
UPDATE_PC_AND_TOS_AND_CONTINUE(3, 1);
}
}
}
相關(guān)文章:
java虛擬機(jī)中對象的創(chuàng)建
java虛擬機(jī)中對象的內(nèi)存布局
java虛擬機(jī)中對象的定位