Java對象創建—深入理解Java虛擬機(四)

前言

 Java是一門面向對象的編程語言,無時無刻不在創建對象,因此,對象的創建過程,我們有必要窺伺一二。這樣我們才能真正理解這門語言的精髓所在與奧妙之處。

正文

1.Java對象的創建
 Java的創建並不是我們在使用時,採用new關鍵字這麼簡單而已,當虛擬機遇到了一條new指令以後,會先去檢查是否能夠在常量池定位到一個類的符號引用,然後檢查類是否已經被加載,解析,初始化過,這部分知識在介紹類加載器時會提到的過程。
 在類加載檢查以後,虛擬機開始爲這個新生的對象分配內存。對象所需要的內存大小在類加載完成以後就確定了,爲對象分配內存其實很容易理解,因爲對象是存放在堆中的,我們首先根據需要內存的大小去堆中規劃內存,然後把對象放進去,事實上情況情況可能還要複雜一點。
 爲什麼呢,這跟垃圾收集器方式有關,採用標記回收法,內存清理以後,只是把沒用的對象清理出去,空出內存,但是並沒有合併整理,導致內存塊錯綜複雜,存活的對象都是錯亂的堆放,因此我們需要維護一個列表來記錄更新列表上的記錄,就好像物流倉庫來貨了,哪個貨架上可以擺放,空餘空間多大,倉庫管理員在電腦裏一查,然後告訴你,去哪兒存放,貨要取走,也要告訴管理員,進行記錄。這學術之上被稱爲“空閒列表”。
 還有一種是垃圾收集器採用了標記整理,清理完內存以後,將存活對象的內存塊整理擺放,指針指向空餘內存塊,內存塊整齊劃一,我們只需要通過移動指針,提供所需的內存給對象就可以了,管理員經常整理貨倉,把貨物都往前整理,空出後面的內存給後面的貨物,我們叫做“指針碰撞”。
 在後續介紹中會給大家介紹垃圾收集器的幾種方式,在這裏我們先把篇幅放在對象上。
 劃分完空間以後,就要把對象的指針指向那片內存,因爲創建對象在虛擬機中是一件非常頻繁的行爲,也就是高併發行爲,那麼就容易出錯,A對象剛剛分配好內存,指針還沒來得及修改,B就過來了,使用了原來的指針來分配內存造成錯誤,解決方法可以對分配空間的動作進行同步處理,虛擬機上採用的CAS加上失敗重試,來確保原子性,另一種是給每個線程都分配一塊內存,在哪個線程中創建的對象,就在其分配的內存中給對象分配內存。
 分配完內存以後,在對象頭中還要設置一下信息,類的元數據信息,對象的哈希碼,對象的GC分帶年齡等信息,然後執行對象的init()方法,根據程序的要求對對象進行初始化,一個真正可用的對象纔算生產出來了,所以,這些過程我們是否也需要了解一下呢,從這部分的基礎裏,我們可以發現,還需要融合很多其他的知識來完整的理解這個過程,如果你暫時不清楚們一定要記下來,然後查清楚,因爲這纔是累積,遇到問題,你躲過去,下去還是躲。就從今天開始,摒棄這樣的習慣。


2.對象的內存佈局
 對象的存儲可以分爲三個區域:對象頭;實例數據;對齊填充。
 對象頭:包含兩部分信息,第一部分存儲對象自身的運行時數據,如哈希碼(HashCode)、 GC分代年齡、 鎖狀態標誌、 線程持有的鎖、 偏向線程ID、 偏向時間戳;另一部分是類型指針,指向它的類元數據的指針,虛擬機通過這個指針來確定這個對象是哪個實例。
 實例數據:存儲對象真正信息的場所。
 對齊補充:佔位符,對象數據必須對齊。


3.對象的訪問
 我們是操作reference數據開操作對象的,虛擬機如何來操作這個reference是不一定的,目前主流的方式就是使用句柄和直接指針兩種方式。
 使用句柄訪問的話,需要劃出一個句柄池內存,reference指向了句柄池的地址,通過句柄可以指向對象實例數據和類型數據的具體地址。

圖片說明

 直接指針的話,reference指向對象地址,而在對象中就要保留類型數據的相關信息。
圖片說明
 句柄池相對來說,需要額外維護一塊內存給句柄池,但是,句柄池的好處就是穩定,在垃圾回收以後,對象的地址都會變動,此時只需要改變句柄中的實例數據指針,而不要去管reference。
 直接指針,訪問速度快,節省了內存空間,但是對象訪問頻繁的情況的下,優勢也並不明顯。

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