1. 程式人生 > 程式設計 >new一個物件的時候發生了什麼

new一個物件的時候發生了什麼

一、引言

如你所知,Java是一門面向物件的程式語言。我們平常在寫程式碼的時候也是在不停的操作各種物件,那麼當你在寫出User user = new User();這樣一行程式碼的時候,JVM都做了些什麼呢?

二、瞭解物件

1、記憶體佈局

在Hotspot虛擬機器器中一個物件的記憶體佈局分為三個部分:物件頭、例項資料、對齊填充。

  • 物件頭又有兩部分的資訊,第一部分是用於儲存物件自身的執行資料(HashCode、GC分代年齡、鎖狀態標誌等)。另一部分是型別指標,指向它的類元資料,虛擬機器器通過這個指標確定這個物件是哪個類的例項(如果使用控制程式碼池方式則不會有)。如果是陣列還會有一個記錄陣列長度的如下表所示:

    內容 說明
    Mark Word 物件的hashCode或鎖資訊等
    Class Metadata Address 物件型別資料指標
    Array length 陣列長度

    Mark Word是一個非固定的資料結構以便在極小的空間記憶體儲儘量多的資訊,它會根據物件的狀態複用自己的儲存空間。各狀態下的儲存內容如下表所示:

    標誌位 狀態 儲存內容
    01 未鎖定 物件HashCode、分代年齡
    00 輕量級鎖定 指向鎖記錄的指標
    10 重量級鎖定 指向鎖記錄的指標
    11 GC標記
    01 可偏向 偏向執行緒ID、偏向時間戳、物件分代年齡
  • 例項資料部分是真正儲存的有效資訊,就是在程式碼中定義的各種型別的欄位內容。無論是父類繼承下來的,還是在子類中的。

  • 對齊填充不是必須存在的,僅僅起著佔位符的作用,因為HotSpot虛擬機器器要求物件的起始地址必須是8位元組的整數倍。

2、物件的訪問

Java程式中我們操作一個物件是通過指向這個物件的引用。我們都知道物件存在堆中,這個引用存在虛擬機器器棧中。那麼引用通過什麼方式去定位堆中物件的位置呢?

  • 直接指標法(HotSpot實現):引用中直接儲存的就是堆中物件的地址。好處就是一次定位速度快,缺點是物件移動(GC時物件移動)引用本身需要修改。

  • 控制程式碼法:Java堆中劃分出一部分作為控制程式碼池,引用儲存的是物件的控制程式碼地址,而控制程式碼中包括了物件例項和型別的具體位置資訊。好處是物件移動只會改變控制程式碼中的例項資料指標,缺點是兩次定位。

三、建立物件流程

上面介紹了物件的基本資訊,現在來講一講建立物件的流程:

  1. 當虛擬機器器遇到一條new指令時,會去檢查這個指令的引數能否在常量池中定位到一個類的符號引用,並檢查代表的類是否已經被類載入器載入。如果沒有被載入那麼必須先執行這個類的載入。
  2. 類載入檢查通過後,虛擬機器器將為新物件分配記憶體,物件所需記憶體的大小在類載入後便可以確定。
  3. 記憶體分配完成後,虛擬機器器需要將物件初始化為零值,保證物件的例項變數在程式碼中不賦初始值就能直接使用。類變數在類載入的準備階段初始化為零值。
  4. 對物件頭進行必要資訊的設定,比如如何找到類的元資料資訊、物件的HashCode、GC分代年齡等。
  5. 經過上述操作,一個新的物件已經產生,但是<init>方法還沒有執行,所有的欄位都是零值。這時候需要執行<init>方法(構造方法)把物件按照程式設計師的意願進行初始化。類變數的初始化操作在類載入的初始化階段<clinit>方法完成

分配記憶體有兩種方式:

  • Java堆記憶體是規整的(使用標記整理或帶壓縮的垃圾收集器),使用一個指標指向空閒位置,分配記憶體既將指標移動與分配大小相等的距離
  • 記憶體不是規整的(使用標記清除的垃圾收集器),虛擬機器器維護一個可用記憶體塊列表,分配記憶體時從列表中找到一個足夠大的記憶體空間劃分給物件並更新可用記憶體列表。

無法找到足夠的記憶體時會觸發一次GC

分配記憶體時併發問題解決方案:

  • 對分配記憶體空間的動作進行同步操作---採用CAS失敗重試的方式保證更新操作的原子性。
  • 每個執行緒在堆中預先分配一塊小記憶體,稱為本地執行緒分配緩衝(Thread Local Allocation Buffer,TLAB),哪個執行緒要分配記憶體就在它的TLAB上分配,只有TLAB用完並分配新的TLAB時才需要同步鎖定。通過-XX:+/-UseTLAB引數來設定。

四、建立物件指令重排序問題

A a = new A();

new一個物件的簡單分解動作:

  1. 分配物件的記憶體空間
  2. 初始化物件
  3. 設定引用指向分配的記憶體地址

其中2、3兩步間會發生指令重排序,導致多執行緒時如果在初始化之前訪問物件則會出現問題,單例模式的雙重檢測鎖模式正是會存在這個問題。可以使用volatile來禁止指令重排序解決問題;





如果有技術問題交流歡迎加我個人微信進群,大家一起學習?

二維碼失效新增微訊號:JK1048195848