物件的建立和分配

萌新J發表於2021-03-08

物件的建立

建立方式

1、 new 關鍵字直接建立。 new ObjectName()。

2、通過 Class 反射物件的 newInstance() 方法。ObjectName  obj  =  ObjectName.class.newInstance()。

3、通過 Class 反射物件獲取 Constructor 類,再呼叫其 newInstance() 方法。 ObjectName obj = ObjectName.class.getConstructor.newInstance()。

4、在類實現 Cloneable 介面的前提下,使用物件的 clone() 方法。ObjectName obj = obj.clone()。(如果內部有自定義類屬性,並且想要實現深克隆(新建立的物件和原有的物件不是同一個),那麼就需要讓該屬性類也實現 Cloneable 介面。

5、使用反序列化。(為了避免屬性丟失,需要讓類實現 Serializable 介面)

public static void main(String[] args){
        try {
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(FilePath))
            ObjectName obj = ois.readObject();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

 

物件的記憶體佈局

在物件身上,儲存了關於這個物件的所有資訊。

 

建立過程

1、根據建立物件的資訊去記憶體中存放類資訊的常量池中尋找是否存在要載入的類資訊,如果存在直接建立物件;如果不存在就先進行該類的載入。

2、為物件分配空間。這裡涉及到執行緒位置分配的安全和效率,比較複雜,會在下面詳細來說。

3、初始化分配到對應的位置。

4、設定物件的物件頭。

5、執行 init 方法(執行非靜態代理塊和例項屬性的初始化以及執行例項構造方法)

 

物件的記憶體分配

分配方式

1、指標碰撞:如果 Java 堆記憶體是規整的,也就是物件的建立位置都是緊挨著的,這樣的話直接將指標指示器向空閒方向移動要建立物件大小的距離就可以了。

2、空閒列表:如果 Java 堆記憶體是不規整的,那麼就需要維護一個空閒列表來記錄哪些位置是空閒的以及多大。在分配時就在列表上查詢,找到合適的位置分配。

 

併發安全

由於在堆的執行緒共享的,所以物件的建立分配的空間可能同時也是另外一個執行緒物件建立的分配位置,這就導致了併發問題,所以為了保證物件建立的併發安全,可以有下面兩種方式:

1、在分配空間時進行同步處理(採用 CAS +迴旋鎖的方式來保證)

2、TLAB:新的執行緒建立時會在堆中劃分一塊區域給該執行緒,後面該執行緒建立的物件都會在該位置存放,當空間不足時才使用第一種方式。(HotSpot 使用)。

 

程式碼優化

1、棧上分配。通過逃逸分析判斷建立的物件是否逃逸出方法(也就是這個物件是否在當前方法的外部被呼叫),如果沒有逃逸出方法,那麼就有可能直接在棧上分類空間來儲存。

2、同步省略。JIT 在編譯時會判斷同步塊所使用的鎖物件是否只能被一個執行緒訪問而沒有被髮布到其他的執行緒。如果沒有,那麼 JIT 編譯器在編譯這段程式碼時就會取消這段程式碼的同步。

3、分離物件(標量替換)。有的物件可能不需要作為一個連續的記憶體結構存在也可以被訪問到,那麼物件的部分(或全部)可以不儲存在記憶體,而是儲存在棧中。

標量:無法再被分解的資料。如一個類的基本資料型別屬性。

聚合量:還可以被分解的資料。如一個類的自定義屬性。

 

逃逸分析的不成熟性

關於逃逸分析目前還是處於不穩定的階段,因為無法保證逃逸分析的效能消耗一定高於其節省的效能。簡單來說就是可能執行了逃逸分析,結果發現都是逃逸出方法的物件,這樣逃逸分析並沒有提高效能,同時執行逃逸分析也消耗了一定的效能,造成得不償失。所以,逃逸分析在 JVM 中沒有實現 棧上分配的功能的,但是其還是在 JIT 中起到了優化作用。所以可以說物件都是建立在堆上的。而我們一般所說的物件建立在棧上,實際情況是因為標量替換的作用。

 

實際的物件空間分配過程

首先會判斷是否可以進行標量替換,如果可以直接使用標量替換,然後結束。不可以的話再嘗試在當前執行緒劃分的區域建立,如果區域不夠再嘗試使用 CAS+ 自旋鎖在其他位置劃分,失敗就再次嘗試,直到成功。

 

物件的訪問

Java 程式通過棧上的引用訪問堆中的物件。物件的訪問方式取決於 JVM 虛擬機器上的實現,目前主流的訪問方式是控制程式碼和直接指標。

控制程式碼

控制程式碼相當於一箇中間表,儲存著對應例項物件的地址以及例項資料所對應類資訊的地址。

優勢:比較穩定,當物件被移動後(垃圾回收時移動物件是非常常見的事)時只需要改變控制程式碼中的指標就可以了。控制程式碼本身不需要改變。

 

直接指標

引用直接指向例項物件,在物件上儲存對應的類資訊所在的地址。

優勢:查詢快,在棧上的引用可以很快找到對應的物件。這也是 HotSpot 預設的訪問方式。

相關文章