1. 程式人生 > >JVM對象創建詳解

JVM對象創建詳解

對象 父類繼承 發生 instance 棧區 all 但是 總結 bsp

JVM對象創建是指的java程序使用new操作符或者反射調用newInstance方法實例化對象時在JVM內存區域創建對象的過程,分配了對象的內存空間之後,JVM會給實例變量賦予初始化值,簡要的圖例如下:

技術分享

簡單來說整個過程就是對象創建會首先在Eden區進行內存分配,創建完成之後棧空間中的變量會對其進行引用。但是實際上整個過程遠沒有描述這麽簡單,下面來具體分析一下對象的創建過程。

首先創建的對象需要在Eden區分配內存空間,如果此時堆區的空間是完全規整的且連續的,那麽創建的新對象就直接按照順序分配到指定的位置上,然後通過指針移動就可以指向下一個新的未分配的位置,這種方式成為“指針碰撞”。但是如果堆區空間不連續,這個時候分配的新對象內存也是不連續的,這個時候就沒有能力只通過指針移動指向來實現對象的內存空間分配,而必須開辟一個新的空間用於緩存當前整個堆區中未使用過的內存地址列表,通過這個列表來找到未分配的內存地址,這種方式成為“空閑空間”。是采用“指針碰撞”還是采用“空閑空間”是根據當前JVM堆空間的結構來進行的。以下圖示闡述了兩種方式創建對象時內存分配情況:

指針碰撞:

技術分享

空間空間:

技術分享

創建對象的並發問題:

上述兩個過程為java對象在JVM內存結構中的詳細創建過程,但是java程序天生就是多線程,所以對於創建對象來說會存在競爭(內存分配競爭),如果兩個線程同時創建對象分配內存空間,就會出現相同的內存空間資源成為互相競爭的資源。JVM遇到這種情況有兩種解決辦法,一種的會采用CAS(compare and set)的方式進行內存的分配。另外一種稱為TLAB(Thread Local Allocation Buffer)來解決競爭,這種方式是為每一個線程創建一個對應的堆空間,讓分配內存的操作下放到TLAB中進行。

對象在堆區中的數據結構:

對象在堆區中主要有兩部分組成,一部分是對象的頭,一部分是實例數據。

對象的頭包含了例如GC分代年齡, 對象的hash值,偏向鎖的線程ID,輕量級和重量級鎖(monitor)的指針等,這個部分也稱為mark word,除此之外還有另外一個部分,這個部分是這個對象的類的指針,用於JVM知曉當前實例具體來自於哪一個類。

實例數據包括了對象真正存儲的信息,包含本身字段的內容,從父類繼承下來的內容等等,具體這裏就不再詳述。

對象的訪問規則:

所謂的對象訪問規則實際上指的是虛擬機棧中的本地變量如何引用對象實例的過程(如何通過引用找到實例本身),有兩種方式可以實現,句柄池和直接引用。

句柄池:

在棧空間中並不直接獲取指向堆區中對象實例的指針,而是通過在堆區中維護一個句柄池,通過句柄池的指針再指向最終實例對象,這種方式的好處在於棧空間的指針並不會隨著對象的位置變動而發生變化,因為句柄池已經把這個變化給屏蔽掉了。

技術分享

直接引用:

顧名思義就是沒有維護中間引用而是直接在棧空間通過指針直接指向對象實例。

技術分享

句柄池和直接引用這兩種方式各有優劣,直接引用更加迅速,只有一次指針定位的開銷,而句柄池更加穩定,對象被移動(垃圾回收)後也能夠保證指針不變。

總結:對象的創建過程就是分配內存空間,賦予實例變量初始值並且建立與棧區變量的指針引用關系(通常來說這個關系應為GC Roots)

JVM對象創建詳解