第二章:Java內存區域與內存溢出異常(2)

第二章:Java內存區域與內存溢出異常(2)程序員

3、HotSpot虛擬機對象探祕

1.對象的建立

​ 在Java程序運行過程當中時刻都有對象被建立。在語言層面上,建立對象(例如克隆、反序列化)一般僅僅是一個關鍵字new而已,而在虛擬機中,對象(普通的Java對象,不包括數組和Class字節碼文件對象)的建立又是怎樣一個過程呢?數組

​ 虛擬機遇到一條new指令時,首先檢查這個指令的參數是否能在常量池中定位到一個類的符號引用,並檢查這個符號引用表明的類是否已被加載、解析和初始化過。若是沒有,那必須先執行相應的類加載。安全

​ 在類加載檢查經過後,接下來虛擬機將爲新生對象分配內存。對象所需內存的大小在類加載完成後即可徹底肯定,爲對象分配空間的任務等同於把一塊肯定大小的內存從Java堆中劃分出來。假設Java堆內存是絕對規整的,全部用過的內存都放在一邊,空閒的內存放在另外一邊,中間放一個指針做爲分界點的指示器,那所分配內存就是把指針向空閒空間那邊挪動一段與對象大小相等的距離,這種分配方式成爲「指針碰撞」(Bump the Point)。若是Java堆中的內存並不規整,已使用的內存和空閒的內存相互交錯,虛擬機就必須維護一個列表,記錄上哪些內存塊是可用的,在分配的時候從列表中找到一塊足夠大的空間劃分給對象實例,並更新列表上的記錄,這種分配方式稱爲「空閒列表」(Free List)。選擇哪一種分配方式由Java堆是否規整決定,而Java堆是否規整又由所採用的垃圾收集器是否帶有壓縮整理功能決定。所以,在使用Serial、ParNew等帶Compact過程的收集器時,常採用空閒列表。併發

​ 除了內存劃分,另外一個須要考慮的問題是線程安全問題。建立對象在虛擬機是很是頻繁的行爲,即便僅是修改指針指向的位置,在併發狀況下也並非線程安全的(可能正在給A分配內存,指針還沒來得及改,對象B就又使用了原來的指針來分配內存)。解決方案有兩種:一是對分配內存的行爲作同步處理(實際上虛擬機採用CAS配上失敗重試的方式保證更新操做的原子性)。二是把內存分配的動做按照線程劃分在不一樣的空間之中進行(每一個線程在Java堆中預先分配一小塊內存,稱爲本地線程分配緩衝(Thread Local Allocation Buffer,TLAB)。哪一個線程要分配內存,就在哪一個線程的TLAB中分配,TLAB用完才進行同步鎖定)。佈局

​ 內存分配完成後,虛擬機要將分配到的內存空間初始化爲零值(不包括對象頭)。若是使用TLAB,這一過程也能提早至TLAB分配時進行。線程

​ 接下來,虛擬機要對對象進行必要的設置,例如這個對象是哪一個類的實例、如何才能找到類的元數據信息、對象的HashCode、對象的GC分代年齡等。這些信息存放在對象的對象頭(Object Header)中。根據虛擬機當前運行狀態不一樣,對象頭會有不一樣的設置方式。指針

​ 上述工做完成後,在虛擬機視角看,一個新的對象已經產生了。但從Java程序的視角看,對象建立纔剛開始,init初始化方法還未執行,全部字段都爲零。執行new指令後會接着執行init方法,把對象按照程序員的意願進行初始化,這樣一個真正可用的對象纔算徹底產生出來。對象

對象的建立過程: 類加載檢查-->分配內存(線程安全問題)-->初始化內存空間-->設置對象頭-->init初始化方法blog

2.對象的內存佈局

​ 在HotSpot虛擬機中,對象的內存佈局分爲3塊:對象頭(Header)、實例數據(Instance Data)和對齊填充(Padding)。繼承

2.1對象頭

​ 對象頭包括兩部分信息,第一部分存儲對象自身的運行時數據,如HashCode、GC分代年齡、鎖狀態標誌、線程持有的鎖、偏向線程ID、偏向時間戳等。

​ 對象頭的另外一部分是類型指針,即對象指向它的類元數據的指針,虛擬機經過指針來肯定這個對象是哪一個類的實例。並非全部的虛擬機實現都必須在對象數據上保留類型指針,也即查找對象的元數據信息並不必定要通過對象自己。若是對象是一個數組,那麼在對象頭中還要有一塊用於記錄數組長度的數據,由於虛擬機能夠經過普通Java對象的元數據信息肯定Java對象的大小,可是數組的元數據中沒法肯定數組大小。

2.2實例數據

​ 實例數據部分是對象真正存儲的有效信息,也是在程序代碼中定義的各類類型的字段內容。不管是從父類繼承下來的,仍是在子類中定義的,都須要記錄下來。這部分的存儲順序會受到虛擬機分配策略參數(Field Allocation Style)和字段在Java源碼中定義順序的影響。HotSpot虛擬機默認的分配策略爲longs/doubles,ints,shorts/chars,bytes/booleans,opps(Ordinary Object Pointers),從分配策略能夠看出,相同寬度的字段老是被分配在一塊兒。在這個前提下,父類中定義的變量會出如今子類以前,若是CompactFields參數爲true(默認爲true),那麼子類中較窄的變量也可能會插入到父類變量的空隙之中。

2.3對齊填充

​ 對齊填充並非必然存在的,也沒有特殊含義,它僅僅起着佔位符的做用。HotSpot虛擬機的自動內存管理系統要求對象起始地址必須是8字節的整數倍,也就是對象的大小必須是8的整數倍,而對象頭部分正好是8字節的倍數(1或2倍),所以,當對象實例數據部分沒有對齊時,就須要經過對齊填充來補全。

3.對象的訪問

​ 創建對象是爲了使用,Java程序須要經過棧上的reference數據來操做堆中的具體對象。因爲reference類型在Java虛擬機規範中只規定了一個指向對象的引用,並無定義這個引用應該經過何種方式去定位、訪問堆中對象的具體位置,因此對象訪問的方式也取決於虛擬機的實現。目前主流的訪問方式由使用句柄和直接指針兩種。

  • 若是使用句柄訪問,那麼堆中會劃分一塊內存做爲句柄池reference中存儲的就是對象的句柄地址,而句柄中包含了對象實例數據與類型數據各自的具體地址信息。

  • 若是使用直接指針訪問,那麼堆中對象的佈局就必須考慮如何放置訪問類型數據的相關信息,而reference中存儲的直接就是對象地址。

​ 兩種對象訪問方式各有優點,使用句柄訪問的最大好處就是reference中存儲的是穩定的句柄地址,在對象被移動(垃圾收集時移動對象是很是廣泛的行爲)時只會改變句柄中的實例數據指針,而reference自己不須要修改。

​ 使用直接指針訪問方式最大好處是速度更快,它節省了一次指針定位的時間開銷,因爲對象的訪問在Java中很是頻繁,所以這類開銷聚沙成塔後也是一項可觀的執行成本。

HotSpot虛擬機中是使用直接指針進行對象訪問的。

相關文章
相關標籤/搜索