HotSpot虛擬機中的對象

1 對象的創建

這裏的對象僅限於普通的java對象,不包括數組和Class對象。一般來說,創建對象通常僅僅通過一個new關鍵字。當虛擬機遇到一條new的指令時,首先會去檢查這個指令所帶的參數能不能在常量池中定位到某個類的符號引用,並且檢查這個類是否已經加載完畢,當類加載檢查通過後,就開始爲對象分配內存了,就是上節所提到的,會在java堆中劃分出一塊確定大小的內存。
在分配內存的時候,主要考慮以下兩種情況:一種情況是假設java虛擬機的內存是絕對規整的,即使用過的內存都在一邊,未使用過的內存都在另一邊,中間用一個指針來作爲界線,這時爲對象分配內存時,只需要將指針往未使用內存的方向移動與對象大小相等的距離即可,這種方式稱之爲“指針碰撞”;另外一種情況就是java虛擬機中的內存是零散分佈的,這時就需要有一張表來記錄那塊內存是使用過的,哪塊內存是沒有使用的,當需要爲對象分配內存時,就從中間挑選出一塊足夠大的內存爲對象分配,並且更新以下記錄的列表,這種方式叫做“空閒列表”。
分配完內存之後,虛擬機就需要對分配到的這塊內存初始化爲零值,然後對對象進行一些必要的設置,比如這個對象是哪個類的實例,對象的哈希碼等信息。以上這些工作做完之後,站在虛擬機的角度來看,對象已經產生了,但是站在程序的角度來看,對象纔剛開始創建,接下來就要執行方法了,讓對象按照程序猿的想法進行初始化,至此一個真正可用的對象就產生了。

2 對象的內存佈局

什麼叫對象的內存佈局?就是對象在內存中存儲的佈局。在HotSpot虛擬機中,對象在內存中存儲的佈局可分爲3部分:對象頭、實例數據和對齊填充。

2.1 對象頭

對象頭包含兩部分信息,第一部分是用於存儲對象自身的運行時數據,如哈希碼、GC分代年齡、鎖狀態標誌、線程持有的鎖等,這些數據的長度在32位和64位的虛擬機中分別爲32bit和64bit,官方稱之爲“Mark Word”。另一部分是類型指針,即對象指向它的類元數據指針,虛擬機是通過這個指針來確定該對象是哪個類的實例的,如果對象是一個java數組,那麼對象頭中還必須有一塊記錄數組長度的數據,因爲虛擬機可以通過普通java對象的元數據信息確定java對象的大小,但是無法從數組的元數據信息中確定數組的大小。

2.2 實例數據

實例數據就是對象真正存儲的有效信息,也是在程序中定義的各種類型的字段內容,無論是從父類中繼承下來的,還是在子類中定義的,都需要記錄下來。

2.3 對齊填充

這一部分的數據並不是必然存在的,它沒有什麼特別的含義,只是起着佔位符的作用,由於HotSpot VM要求對象的大小必須是8字節的整數倍,而對象頭部分正好是8字節的倍數,所以當對象實例數據部分不是8字節的整數倍時,就需要這部分的數據來填充補齊了。

3 對象的訪問定位

創建對象就是爲了使用它,java程序需要通過棧上的reference數據來操作堆上的具體對象。目前主流的訪問方式有使用句柄和直接指針兩種。
在這裏插入圖片描述
在這裏插入圖片描述
通過上面兩張圖可以看出,使用句柄訪問對象時,需要在java堆中劃分一塊內存作爲句柄池,句柄池裏面存放的是對象實例和類型數據各自的地址,reference存儲的是對象的句柄池地址,優點就是對象移動時,只需要改變句柄池中實例數據的地址就可以了,reference中存儲的數據不變,缺點是需要兩次指針定位的訪問;使用直接指針訪問對象時,就必須要考慮如何放置訪問類型數據的相關信息了,reference中存儲的就是對象的地址,優點是節省了一次指針定位的時間,缺點是對象移動時,reference中的數據也需要做相應的變化。

4 疑惑點

public class TestStringIntern {

	public static void main(String[] args) {
		String str1 = new StringBuilder("計算機").append("軟件").toString();
		System.out.println(str1.intern() == str1);
		
		String str2 = new StringBuilder("ja").append("va").toString();
		System.out.println(str2.intern() == str2);
	}
}

上面這串代碼在jdk1.6和jdk1.7執行的結果不一致,官方給出的解釋是:在jdk1.6中,intern()方法會把首次遇到的字符串實例複製到永久代中,返回的也是永久代中這個字符串實例的引用,而用StringBuilder創建的字符串實例在Java堆上,所以必然不是同一個引用,將返回false。而jdk1.7中的intern()實現不會再複製實例,只是在常量池中記錄首次出現的實例引用,因此intern()返回的引用和由StringBuilder創建的那個字符串實例是同一個。對str2比較返回false是因爲“java”這個字符串在執行StringBuilder.toString()之前已經出現過,字符串常量池中已經有它的引用了,不符合首次出現的原則,而“ni hao”這個字符串則是首次出現的,因此返回true。
前半部分還能理解,後半部分始終不理解,“java”字符串怎麼會已經出現過了呢?難道說jdk7及以後的版本中,jvm默認堆中已經有“java”這個字符串了???希望有哪位大神給解釋一下。

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