天天看點

Java對象建立、配置設定、布局、通路小析(HotSpot虛拟機)(一)

java對象的建立有幾種方式呢(這裡所說的java對象僅限于普通java對象不包含資料和class對象)?大緻有以下四種方式:

new關鍵字。這應該是我們最常見和最常用最簡單的建立對象的方式。

使用newinstance方法。這裡包括class的newinstance方法和constructor的newinstance方法(class的newinstance方法最終調用的也是constructor的newinstance方法)。

使用clone方法。要使用clone方法我們必須實作實作cloneable接口,用clone方法建立對象并不會調用任何構造函數。即我們所說的淺copy。

反序列化。要實作反序列化我們需要讓我們的類實作serializable接口。當我們序列化和反序列化一個對象,jvm會給我們建立一個單獨的對象,在反序列化時,jvm建立對象并不會調用任何構造函數。即我們所說的深copy。

,然後檢查這個符号引用代表的類是否已被加載、解析、和初始化過。

當上面的檢查通過之後,虛拟機就會為新生對象配置設定記憶體了。這裡需要注意的是:對象所需記憶體的大小在類加載完成後便可以完全确定了。既然對象所需的記憶體大小可以确定了,那為對象配置設定記憶體空間就相當于從java堆中找一份相應大小的記憶體空間了。由于不同的虛拟機所采用的垃圾收集算法不同,或者相同的虛拟機根據不同的配置所采用不同的垃圾收集算法,是以會導緻jvm中的記憶體可能是規整(有一塊連續的記憶體空間)或者不規整(記憶體一個碎塊一個碎塊的)(使用serial、parnew等帶compact過程的收集器時,java記憶體是相對規整的,使用cms這種基于mark-sweep算法的收集器時,java記憶體是相對不規整的。這一部分的内容參考垃圾收集器)。對于記憶體規整的,對象記憶體配置設定方式為“指針碰撞”,對于不規整的記憶體,對象記憶體配置設定方式為空閑清單方法。

由于java堆中的記憶體是絕對規整(具體的參看标記壓縮的垃圾回收機制)的,所有用過的記憶體都放在一邊,空閑的記憶體放在另一邊,中間放着一個指針作為分界點的訓示器,那所配置設定的記憶體就僅僅是把那個指針指向空閑空間那邊,然後挪動一段與對象大小相等的距離。

由于java堆中的記憶體是不規整的(具體的參看标記清除的垃圾回收機制)的,正在使用的記憶體和空閑的記憶體是交織在一起的,這個時候虛拟機會維護一個清單,在這個清單中會記錄那些記憶體塊是可以使用的,所在在配置設定記憶體的時候,隻需要從清單中找到一塊足夠大的空間劃分給對象就行了。

上面說的是兩種為對象配置設定記憶體的方式,你以為有這兩種記憶體配置設定方式就行了嗎?圖樣圖森破。隻要做過項目的人都知道,對象的建立時多麼頻繁的一件事,是以這麼頻繁的建立對象就會産生線程安全的問題。有可能我現在正在給a對象配置設定記憶體,指針還沒來得及修改,對象b又同時使用了原來的指針來進行記憶體配置設定。是以這個時候就需要考慮線程安全的問題了。jvm解決這個問題有兩種方式,一種方式是使用cas算法,另一種是使用tlab(thread

local allocation buffer 本地線程配置設定緩沖)。即,把記憶體的配置設定動作按照線程劃分在不同的空間之中進行,也就是每個線程在java堆中預先配置設定一小塊記憶體。哪個線程需要配置設定記憶體,就在哪個線程的tlab上配置設定。

在記憶體配置設定完成之後,需要做的一件事是為屬性賦初始值。然後虛拟機會對對象進行一些必要的設定,例如這個對象是哪個類的執行個體、如何才能找到類的中繼資料資訊、對象的哈希碼、對象的gc分代年齡等資訊,這些資訊就存放在對象頭中。對象頭就是我們下節所要讨論的内容。