對象創建過程
概述
通常情況下,我們創建一個對象,只需要使用new關鍵字即可。而對於java虛擬機來說,需要經歷一系列過程。 首先,需要找到對應的類是哪個,這個類是否已經載入,沒有載入還需要將它先載入進來,然後給將要創建的對象分配記憶體,然後對對象進行初始化設置,我們才能使用一個完整的對象。
查找類
分配記憶體
經過查找類的一系列操作之後,java虛擬機知道了創建哪個類的對象,這時候需要記憶體來放置將要創建的對象。所以接下來需要分配記憶體,一個類載入進來之後,對象所需要的記憶體已經確定,現在需要找到一塊空閑區域劃分給將要產生的對象。
分配方式
指針碰撞方式
這種方式需要將使用過的記憶體放在一邊,未使用的放在另一邊,指針指向第一個空閑位置,這時候分配記憶體只需要將指針往空閑位置移動對象大小位置即可。但是我們知道,記憶體有分配和回收,當不斷的進行分配和回收之後,記憶體就不是連續的,如果我們使用這種方式顯然行不通,所以我們需要採用的垃圾處理器帶有空間壓縮整理的功能,例如:Serial,ParNew
空閑列表方式
還有一種方式,空閑列表方式,根據名稱我們都知道,用一個列表記錄所有的空閑塊,這個需要單獨維護一個列表。CMS這種基於清除演算法的收集器,就是採用了這個方式。但是有進行進一步的優化,為了能在大多數情況下分配的更快,設計了一個分配緩衝區,從空閑列表拿下一塊較大的記憶體,然後在這塊記憶體內部採用指針碰撞方式來進行分配
並發問題
對象創建在虛擬機中是非常頻繁的行為,即使使用最簡單的指針碰撞方式,僅僅修改指針所指向的位置,在並發情況下也不是執行緒安全的(如果正在給對象A分配記憶體,還沒修改指針位置,對象B也需要分配記憶體,相當於使用錯誤的指針位置)。
同步處理
一種處理方式是進行同步處理,實際上虛擬機採用CAS+失敗重試的方式保證更新操作的原子性。當對象A正在分配記憶體時,標識正在進行分配,對象B來時發現正在分配,則分配失敗,繼續重試,請求分配,直到對象A分配完畢,對象B繼續進行分配。
空間隔離
另一種方式是把記憶體分配的動作按照執行緒劃分在不同的空間中進行,即給每一個執行緒預先分配一小塊記憶體,稱為本地執行緒分配緩衝(Tread Local Allocation Buffer TLAB),哪個執行緒需要分配記憶體時候,現在自己的本地緩衝區中進行分配,不夠了再進行同步鎖定方式。虛擬機是否使用TLAB可以通過- XX:+/-useTLAB參數進行設定。
初始化
當給對象分配了空間之後,我們需要對對象進行初始化操作,初始化對象的有三個步驟
預初始化
將除了對象頭以外的所有記憶體空間初始化為零值(如果使用了TLAB,這一步操作可以提前到TLAB分配是進行),這步操作保證了對象的實例欄位在java程式碼中可以不賦值直接使用。
設置對象頭
對象頭,既然位於最前面,應該存儲一些描述對象的基本資訊,其中包括該對象是哪個類的實例,如何找到類的元資訊,對象的hash碼(實際不是在這裡進行設置,而是在調用Object::hashCode()時計算),對象的GC分代年齡,鎖等資訊。到這一步,在虛擬機層面一個新的對象已經產生了。
初始化
最後一步,才是按照開發人員真正的意願去構造對象需要的其他資源(父類等)和狀態資訊,這樣一個真正可用的對象才構建出來。
總結
對象的創建過程很簡單,就是找到創建依據(類),放在哪裡(分配記憶體),初始化(設置我們想要的東西)。