標籤:原子性 符號 java代碼 需要 程式 初始 div 一個 載入
Java是一門物件導向的語言,Java程式運行過程中無時無刻都有對象被建立出來。在語言層面上,建立對象(複製、還原序列化)就是一個new關鍵字而已,但是虛擬機器層面上卻不是如此。我們看一下在虛擬機器層面上建立對象的步驟:
(1)虛擬機器遇到一條new指令,首先去檢查這個指令的參數能否在常量池中定位到一個類的符號引用,並且檢查這個符號引用代表的類是否已經被載入、解析和初始化。如果沒有,那麼必須先執行類的初始化過程。
(2)類載入檢查通過後,虛擬機器為新生對象分配記憶體。對象所需記憶體大小在類載入完成後便可以完全確定,為對象分配空間無非就是從Java堆中劃分出一塊確定大小的記憶體而已。這個地方會有兩個問題:
①如果記憶體是規整的,那麼虛擬機器將採用的是指標碰撞法來為對象分配記憶體。意思是所有用過的記憶體在一邊,閒置記憶體在另外一邊,中間放著一個指標作為分界點的指標,分配記憶體就僅僅是把指標向空閑那邊挪動一段與對象大小相等的距離罷了。如果垃圾收集器選擇的是Serial、ParNew這種基於壓縮演算法的,虛擬機器採用這種分配方式。
②如果記憶體不是規整的,已使用的記憶體和未使用的記憶體相互交錯,那麼虛擬機器將採用的是空閑列表法來為對象分配記憶體。意思是虛擬機器維護了一個列表,記錄上哪些記憶體塊是可用的,再分配的時候從列表中找到一塊足夠大的空間劃分給對象執行個體,並更新列表上的內容。如果垃圾收集器選擇的是CMS這種基於標記-清除演算法的,虛擬機器採用這種分配方式。
另外一個問題及時保證new對象時候的執行緒安全性。因為可能出現虛擬機器正在給對象A分配記憶體,指標還沒有來得及修改,對象B又同時使用了原來的指標來分配記憶體的情況。虛擬機器採用了CAS配上失敗重試的方式保證更新更新操作的原子性和TLAB兩種方式來解決這個問題。
(3)記憶體配置結束,虛擬機器將分配到的記憶體空間都初始化為零值(不包括對象頭)。這一步保證了對象的執行個體欄位在Java代碼中可以不用賦初始值就可以直接使用,程式能訪問到這些欄位的資料類型所對應的零值。
(4)對對象進行必要的設定,例如這個對象是哪個類的執行個體、如何才能找到類的中繼資料資訊、對象的雜湊碼、對象的GC分代年齡等資訊,這些資訊存放在對象的對象頭中。
(5)執行<init>方法,把對象按照程式員的意願進行初始化。
到此,一個真正可用的對象才算完全產生出來。
上述建立對象是為了使用對象,Java程式需要通過棧上的reference(引用)資料來操作堆上的具體對象。比如我們寫了一句:
Object obj = new Object();
在new Object()之後,其實有兩部分內容,一部分是類資料(比如代表類的Class對象),一部分是執行個體資料。
由於reference在Java虛擬機器規範中只是一個指向對象new Object()的引用obj,並沒有規定obj應該通過何種方式去定位,以及訪問堆中對象的具體位置,所以對象訪問方式也是取決於虛擬機器而定的。主流方式有兩種:
(1)控制代碼訪問。Java堆中劃分出一塊控制代碼池,obj指向的是對象的控制代碼地址,控制代碼中則包含了類資料的地址和執行個體資料的地址
(2)指標訪問。對象中儲存所有的執行個體資料和類資料的地址,obj指向的是這個對象
HotSpot虛擬機器採用的是後者,不過前者的對象訪問方式也是十分常見的。
細談Java對象建立