java對象的創建、內存佈局和訪問定位

java是一門面向對象的編程語言,在java程序運行過程中無時無刻都有對象被創建。
創建對象的方式有多種:new、克隆、反序列化

1、JVM中創建對象的過程

虛擬機遇到一條new指令,開始通過以下步驟創建對象:
第一步: 檢查這個指令的參數是否能在常量池中定位到一個類的符號引用,並且檢查這個符號引用代表的類是否完成加載、解析和初始化過程,如果沒有,則必須先執行類加載過程
第二步: 類加載檢查通過後,虛擬機將爲新生對象分配內存。對象所需的內存空間大小在類加載完成後就可以完全確定。
爲對象分配內存空間時,根據java堆內存是否規整可分爲兩種方式。而java堆內存是否規整與使用的垃圾收集器的種類有關。用Serial、ParNew等採用壓縮複製算法的垃圾收集器的java堆內存空間是規整的;而用CMS這種標記清除算法的垃圾收集器的堆內存空間則不是規整的。

  • 指針碰撞:適用於java堆內存規整的情況。所有用過的內存都放在一邊,空閒的內存放在另一邊,中間放着一個指針作爲分界點的指示器。分配內存僅僅是將指針向空閒的一邊挪動一段與對象所需空間相等的距離。
  • 空閒列表:適用於java堆內存不規整的情況。java堆內存使用過的內存和空閒內存相互交錯,無法使用指針碰撞,所以虛擬機自己維護一個列表,記錄空閒內存塊的地址,再爲對象分配內存的時候直接從列表中找一塊足夠大的內存分配。

分配空間的線程安全問題:創建對象在虛擬機中是非常頻繁的行爲,在併發情況下,多線程共享堆內存,可能出現正在給A分配內存,指針還沒來得及修改,其他線程又使用了原來的指針給對象B分配內存的情況。
解決這個問題有兩種方案:

  • 對分配內存空間的動作進行同步處理。實際上虛擬機採用CAS加上失敗重試的方式保證更新操作的原子性
  • 把內存分配的動作按照線程劃分在不同的內存空間之中進行,稱爲本地線程分配緩衝(Thread Local Allocation Buffer,TLAB)。

虛擬機是否使用TLAB,可以通過-XX:=/-UseTLAB參數來設定。

**第三步:**內存分配完後,虛擬機將需要分配到的內存空間都初始化爲零值(不包括對象頭),這一操作保證了對象的實例字段在java代碼中可以不賦初始值就直接使用,程序也能訪問到這些字段的數據類型零值。
第四步: 虛擬機爲對象設置對象頭信息,包括這個對象所屬的類、如何找到類的元數據信息、對象的哈希碼、對象的GC分代年齡等。

經過這四步,對象就在內存中被創建出來,可以執行<init>方法進行初始化。

2、對象的內存佈局

在HotSpot虛擬機中,對象在內存中的存儲佈局可以分爲三個區域:對象頭、實例數據和對齊填充。
對象頭
對象頭包括對象運行時區域和指向類元數據的指針這兩部分信息。對象運行時數據包括哈希嗎、GC分代年齡、鎖狀態標誌、線程持有的鎖、偏向線程ID、偏向時間戳等信息。類型指針數據用來確定這個對象是哪個類的實例,存儲指向方法區中對應類型信息的指針。
實例數據
對象真正存儲的有效信息,也是程序代碼中定義的各種字段類型的內容。
對齊填充
僅僅是起着佔位符的作用,沒有特別含義。HotSpot虛擬機內存管理要求對象的起始地址必須是8字節的整數倍。對象實例數據部分沒有對齊的時候就需要通過對齊填充來補全。

3、對象的訪問定位

建立對象是爲了使用對象,java程序通過棧上的reference數據來操作堆上的具體對象。訪問堆中的具體對象方式有使用句柄和直接指針兩種。
使用句柄
該方式java堆中會劃分一塊內存來作爲句柄池,reference中存儲着對象的句柄地址,而句柄中包含了對象實例數據和類型數據各自具體的地址信息。
優勢:使用句柄方式訪問對象的最大好處是reference中存儲的是穩定的句柄地址,在對象被移動時只需修改句柄中的實例數據指針,而棧中的reference本身不用需改。
直接指針
如果使用直接指針訪問,java堆對象的實例數據中就必須包含訪問類型數據的指針,而reference中存儲的直接就是對象的地址。
優勢:使用直接指針訪問方式的最大好處是速度快。節省了一次指針定位的開銷。

HotSpot虛擬機採用直接指針的方式進行對象訪問。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章