- 對象創(chuàng)建
這里的對象僅僅是普通java對象,不包括數(shù)組摇天、Class對象
- 類
package com.wkh; public class Test { public Test(int i, String bb) { } }
package com.wkh; public class Main { public static void main(String[] args) { new Test(23,"bb"); } }
-
public static void main(String[] args)
的class字節(jié)碼
0: new #2 // class com/wkh/Test 3: dup 4: bipush 23 6: ldc #3 // String bb 8: invokespecial #4 // Method com/wkh/Test."<init>":(ILjava/lang/String;)V 11: pop 12: return
- 對象創(chuàng)建過程
當(dāng)虛擬機(jī)遇到new指令的時(shí)候
- 首先去檢查這個(gè)指令的參數(shù)(即
#2
)是否能在常量池中定義到一個(gè)類的符號引用- 檢查這個(gè)符號引用代表的類是否已經(jīng)被加載、解析和初始化過并炮,如果沒有坤溃,那必須先執(zhí)行相應(yīng)的類加載過程
- 類加載檢查通過后,接下來虛擬機(jī)將在為新生對象分配內(nèi)存昧互。對象所需內(nèi)存的大小在類加載完成后便可以確定挽铁,為對象分配空間的任務(wù)等同于把一塊確定大小的內(nèi)存從java堆中劃分出來。
- 內(nèi)存分配完成后敞掘,虛擬機(jī)需要將分配到的內(nèi)存空間都初始化為零(不包括對象頭)叽掘,如果使用TLAB,這一操作也可以提前至TLAB分配時(shí)進(jìn)行玖雁。這一步操作保證了對象的實(shí)例字段在java代碼中不賦初值就可以直接使用更扁,程序能訪問到這些字段的數(shù)據(jù)類型所對應(yīng)的零值。
- 接下來,虛擬機(jī)對對象進(jìn)行必要的設(shè)置疯潭,例如這個(gè)對象是哪個(gè)類的實(shí)例赊堪、如果才能找到類的元數(shù)據(jù)信息面殖、對象的哈希碼竖哩、對象的GC分代年齡等信息。這些信息存放在對象的對象頭(Object Header)之中脊僚。根據(jù)虛擬機(jī)當(dāng)前運(yùn)行狀態(tài)的不同相叁,如是否啟動偏向鎖等,對象頭會有不同的設(shè)置方式辽幌。
說明:
1-5
是new
指令的過程
- 上面的工作完成后增淹,從虛擬機(jī)的視角來看,一個(gè)新的對象已經(jīng)產(chǎn)生了乌企,但從java程序的視角來看虑润,對象創(chuàng)建才剛剛開始,因?yàn)?code><init>方法還沒有執(zhí)行加酵,所有的字段都是零值拳喻。所以,一般來說猪腕,執(zhí)行
new
指令之后冗澈,還會接著執(zhí)行<init>
方法,如上面的8: invokespecial #4 // Method com/wkh/Test."<init>":(ILjava/lang/String;)V
陋葡。
- 下面是對象創(chuàng)建過程
new
指令的源碼
等同于
對象創(chuàng)建過程
的1-5
//jdk7u-dev/hotspot/src/share/vm/interpreter/bytecodeInterpreter.cpp //確保常量池中存放的是已解釋的類亚亲。 if (!constants->tag_at(index).is_unresolved_klass()) { // 斷言確保是 klassOop 和 instanceKlassOop oop entry = constants->slot_at(index).get_oop(); 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(); // 確保對象所屬類已經(jīng)經(jīng)過初始化階段 if ( ik->is_initialized() && ik->can_be_fastpath_allocated() ) { // 取對象長度 size_t obj_size = ik->size_helper(); oop result = NULL; // 如果TLAB沒有把內(nèi)存中的值置零,那么這里就需要置零 bool need_zero = !ZeroTLAB; // 是否使用TLAB腐缤,是否在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指令捌归,這里是一個(gè)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) { // 如果需要门扇,則為對象初始化零值 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); } } }
- java堆分配內(nèi)存的2種方式
- 指針碰撞(Bump the Point)
如果java堆中內(nèi)存是絕對規(guī)整的偿渡,用過的內(nèi)存放一邊臼寄,空閑的內(nèi)存放另一邊,中間放著指針作為邊界溜宽,那么分配內(nèi)存僅僅是把指針向著空閑那邊挪動一段與對象大小相等距離吉拳,這種分配方式叫做指針碰撞
。- 空閑列表(Free List)
如果java堆不是絕對規(guī)整的适揉,已使用和空閑的內(nèi)存交錯(cuò)留攒,那么虛擬機(jī)就必須維護(hù)列表煤惩,上面記錄哪些內(nèi)存塊是可用的,在分配的時(shí)候找一塊足夠大空間劃分給對象實(shí)例炼邀,并且更新表上記錄魄揉,這種分配方式叫做空閑列表
。- 采用哪種內(nèi)存分配方式
選擇哪種分配方式是由java堆是否規(guī)整決定的拭宁,而java堆是否規(guī)整由垃圾收集器是否帶有壓縮整理功能
決定洛退。因此,在使用Serial杰标、ParNew等帶Compact過程的收集器時(shí)兵怯,系統(tǒng)采用指針碰撞;而使用CMS這種基于Mark-Sweep算法的收集器時(shí)腔剂,通常采用空閑列表媒区。
- 關(guān)于java堆分配內(nèi)存時(shí)線程不安全的的問題
- 例子
正在給對象A分配內(nèi)存,指針還未進(jìn)行修改掸犬,B同時(shí)又使用了原來的指針分配內(nèi)存袜漩。- 解決辦法1-
同步
對分配內(nèi)存空間的動作進(jìn)行同步處理,實(shí)際上虛擬機(jī)采用CAS配上失敗重試的方式保證更新操作的原子性- 解決辦法2-
TLAB
把內(nèi)存分配的動作按照線程劃分在不同的空間中進(jìn)行登渣,即每個(gè)線程在java堆中預(yù)先分配一小塊內(nèi)存噪服,稱為線程私有分配緩沖(Thread Local Allocation Buffer,TLAB)胜茧。
哪個(gè)線程需要分配內(nèi)存粘优,就在哪個(gè)線程的TLAB上分配,只有TLAB用完并分配新的TLAB時(shí)呻顽,才需要同步鎖定雹顺。虛擬機(jī)是否使用TLAB,可以通過-XX:+/-UseTLAB
參數(shù)設(shè)定廊遍。