Java基礎之創建虛擬機對象的過程詳細總結
一、對象的創建
1.1 new 類名
虛擬機遇到一條new指令時,
首先檢查這個指令的參數是否能在常量池中定位到一個類的符號引用,並檢查這個符號引用代表的類是否已經被加載、解析和初始化過
。如果沒有,先執行相應的類加載過程
。
1.2 分配內存
虛擬機為新生對象分配內存。
對象所需內存大小在類加載完成後就可以確定,為對象分配內存等同於把一塊確定大小的內存從Java堆中劃分出來
。
(1)內存分配的方式有兩種:
①
指針碰撞
: java堆如果規整,一邊是用過的內存,一邊是空閑的內存,中間一個指針作為邊界指示器; 分配內存隻需向空閑那邊移動指針空出與對象大小相等的空間;②
空閑列表
: 如果不規整,即用過的和空閑的內存相互交錯;則虛擬機需要維護一個列表,記錄哪些內存可用;分配內存時查表找到一個足夠大的內存,並更新列表記錄。
選擇哪種分配方式是根據這個虛擬機所采用的垃圾收集器是否帶有壓縮整理功能決定的
:如果虛擬機的虛擬器帶壓縮整理功能,則系統采用指針碰撞的內存分配算法;否則采用空閑列表的算法。
(2)線程安全問題
並發時,上面兩種方式分配內存的操作都不是線程安全的,有兩種解決方案:
①同步處理
JVM采用CAS(Compare and Swap)機制加上失敗重試的方式,保證更新操作的原子性;
CAS:有3個操作數,內存值V,舊的預期值A,要修改的新值B。當且僅當預期值A和內存值V相同時,將內存值V修改為B,否則什麼都不做;
②本地線程分配緩沖區(TLAB)
把分配內存的動作按照線程劃分在不同的空間中進行:每個線程在Java堆預先分配一小塊內存,稱為本地線程分配緩沖區(Thread Local Allocation Buffer,TLAB);哪個線程需要分配內存就從哪個線程的TLAB上分配;隻有TLAB用完需要分配新的TLAB時,才需要同步處理。
JVM通過”-XX:+/-UseTLAB”指定是否使用TLAB。
1.3 初始化零值
內存分配完之後,虛擬機需要將分配到的內存空間都初始化為零值。如果用TLAB,則在TLAB分配時進行。這
保證瞭程序中對象(及實例變量)不顯式初始賦零值,程序也能訪問到零值
。
1.4 設置對象信息
虛擬機對對象進行必要的設置,例如這個對象是哪個類的實例、 如何才能找到類的元數據信息、 對象的哈希碼、 對象的GC分代年齡等信息。這些信息存放在對象的對象頭(Object Header)之中。
1.5 構造對象
執行init方法,即按照程序員的意願進行初始化
。至此真正可用的對象才算完全被構造出來。
二、對象的內存佈局
在HotSpot虛擬機中,對象在內存中存儲的佈局可以分為3塊區域:
對象頭
(Header)、實例數據
(InstanceData)和對齊填充
(Padding)。
2.1 對象頭
HotSpot虛擬機的對象頭包含兩部分:
(1)第一部分用於存儲對象自身運行時數據
,這部分數據的長度在32位和64位的虛擬機中分別為32bit和64bit,官方稱它為“Mark Word”。
(2)另外一部分是類型指針
,即對象指向它的類元數據的指針,虛擬機通過這個指針來確定這個對象是那個類的實例
。
並不是所有的虛擬機實現都必須在對象數據上保留類型指針,即查找對象的元數據信息並不一定要經過對象本身。
另外,如果對象是一個Java數組,那在對象頭中還必須有一塊用於記錄數組長度的數據,因為虛擬機可以通過普通Java對象的元數據信息確定Java對象的大小,但是從數組的元數據中卻無法確定數組的大小。
2.2 實例數據
實例數據部分是
對象真正存儲的有效信息
,也是在程序代碼中所定義的各種類型的字段內容。無論是從父類繼承下來的,還是在子類中定義的,都需要記錄起來
。這部分的存儲順序會受到虛擬機分配策略參數(FiedsAllocationStyle)和字段在Java源碼中定義順序的影響。
HotSpot虛擬機默認的分配策略為:longs/doubles、ints、shorts/chars、bytes/booleans、oops(Ordinary Object Pointers)。從分配策略中可以看出,相同寬度的字段總是被分配到一起
。在滿足這個前提條件的情況下,在父類中定義的變量會出現在子類之前
。如果CompactFieds參數值為true(默認為true),那麼子類中較窄的變量也可能會插入到父類變量的空隙之中。
2.3 對齊填充
對齊填充並不是必然存在的,也沒有特別的含義,他僅僅
起占位符的作用
。由於HotSpot VM的自動內存管理系統要求對象起始地址必須是8字節的整數倍,換句話說,就是對象的大小必須是8字節的整數倍,而對象頭部分正好是8字節的倍數,因此,當對象實例部分沒有對齊時,就需要通過對齊填充來補全
。
三、對象的訪問定位
建立對象是為瞭使用對象,Java程序通過棧上的reference數據來操作堆上的具體對象
。
reference類型在Java虛擬機規范中之規定瞭一個指向對象的引用,但沒有定義這個引用應該通過何種方式去定位訪問隊中的對象的具體位置,因此對象的訪問方式也是由虛擬機實現而定的。目前主流方式是使用句柄和直接指針兩種。
3.1 使用句柄
如果以句柄方式訪問,
Java堆中將會劃分出一塊內存作為句柄池,reference中存儲的就是對象的句柄地址,而句柄中包含瞭對象實例數據與類型數據各自的具體地址信息
。
3.2 指針方式
如果以指針方式訪問,那麼Java堆對象的佈局中就必須考慮如何放置訪問類型數據的相關信息,而
reference中存儲的直接就是對象地址
,如果隻是訪問對象本身,就會少一次間接訪問的開銷。
四、兩種方式的比較
句柄訪問最大好處就是
reference中存儲的是穩定的句柄地址,在對象被移動時隻會改變句柄中的實例數據指針,而reference本身不需要修改
。
指針訪問方式最大好處就是速度更快,節省瞭一次指針定位的時間開銷
,由於對於下部分的訪問在Java中非常頻繁,因此此類開銷積少成多後也是一項非常可觀的執行成本。
到此這篇關於Java基礎之創建虛擬機對象的過程詳細總結的文章就介紹到這瞭,更多相關虛擬機中對象的創建過程內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- Java對象在內存中的佈局是如何實現的?
- 關於JVM翻越內存管理的墻
- JAVA JVM運行時數據區詳解
- 淺談java object對象在heap中的結構
- Java synchronized重量級鎖實現過程淺析