詳解jvm對象的創(chuàng)建和分配
對象的創(chuàng)建
創(chuàng)建方式
1、 new 關鍵字直接創(chuàng)建。 new ObjectName()。
2、通過 Class 反射對象的 newInstance() 方法。ObjectName obj = ObjectName.class.newInstance()。
3、通過 Class 反射對象獲取 Constructor 類,再調用其 newInstance() 方法。 ObjectName obj = ObjectName.class.getConstructor.newInstance()。
4、在類實現(xiàn) Cloneable 接口的前提下,使用對象的 clone() 方法。ObjectName obj = obj.clone()。(如果內部有自定義類屬性,并且想要實現(xiàn)深克?。ㄐ聞?chuàng)建的對象和原有的對象不是同一個),那么就需要讓該屬性類也實現(xiàn) Cloneable 接口。
5、使用反序列化。(為了避免屬性丟失,需要讓類實現(xiàn) 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(); } }
對象的內存布局
在對象身上,存儲了關于這個對象的所有信息。
創(chuàng)建過程
1、根據創(chuàng)建對象的信息去內存中存放類信息的常量池中尋找是否存在要加載的類信息,如果存在直接創(chuàng)建對象;如果不存在就先進行該類的加載。
2、為對象分配空間。這里涉及到線程位置分配的安全和效率,比較復雜,會在下面詳細來說。
3、初始化分配到對應的位置。
4、設置對象的對象頭。
5、執(zhí)行 init 方法(執(zhí)行非靜態(tài)代理塊和實例屬性的初始化以及執(zhí)行實例構造方法)
對象的內存分配
分配方式
1、指針碰撞:如果 Java 堆內存是規(guī)整的,也就是對象的創(chuàng)建位置都是緊挨著的,這樣的話直接將指針指示器向空閑方向移動要創(chuàng)建對象大小的距離就可以了。
2、空閑列表:如果 Java 堆內存是不規(guī)整的,那么就需要維護一個空閑列表來記錄哪些位置是空閑的以及多大。在分配時就在列表上查詢,找到合適的位置分配。
并發(fā)安全
由于在堆的線程共享的,所以對象的創(chuàng)建分配的空間可能同時也是另外一個線程對象創(chuàng)建的分配位置,這就導致了并發(fā)問題,所以為了保證對象創(chuàng)建的并發(fā)安全,可以有下面兩種方式:
1、在分配空間時進行同步處理(采用 CAS +回旋鎖的方式來保證)
2、TLAB:新的線程創(chuàng)建時會在堆中劃分一塊區(qū)域給該線程,后面該線程創(chuàng)建的對象都會在該位置存放,當空間不足時才使用第一種方式。(HotSpot 使用)。
代碼優(yōu)化
1、棧上分配。通過逃逸分析判斷創(chuàng)建的對象是否逃逸出方法(也就是這個對象是否在當前方法的外部被調用),如果沒有逃逸出方法,那么就有可能直接在棧上分類空間來保存。
2、同步省略。JIT 在編譯時會判斷同步塊所使用的鎖對象是否只能被一個線程訪問而沒有被發(fā)布到其他的線程。如果沒有,那么 JIT 編譯器在編譯這段代碼時就會取消這段代碼的同步。
3、分離對象(標量替換)。有的對象可能不需要作為一個連續(xù)的內存結構存在也可以被訪問到,那么對象的部分(或全部)可以不存儲在內存,而是存儲在棧中。
標量:無法再被分解的數(shù)據。如一個類的基本數(shù)據類型屬性。
聚合量:還可以被分解的數(shù)據。如一個類的自定義屬性。
逃逸分析的不成熟性
關于逃逸分析目前還是處于不穩(wěn)定的階段,因為無法保證逃逸分析的性能消耗一定高于其節(jié)省的性能。簡單來說就是可能執(zhí)行了逃逸分析,結果發(fā)現(xiàn)都是逃逸出方法的對象,這樣逃逸分析并沒有提高性能,同時執(zhí)行逃逸分析也消耗了一定的性能,造成得不償失。所以,逃逸分析在 JVM 中沒有實現(xiàn) 棧上分配的功能的,但是其還是在 JIT 中起到了優(yōu)化作用。所以可以說對象都是創(chuàng)建在堆上的。而我們一般所說的對象創(chuàng)建在棧上,實際情況是因為標量替換的作用。
實際的對象空間分配過程
首先會判斷是否可以進行標量替換,如果可以直接使用標量替換,然后結束。不可以的話再嘗試在當前線程劃分的區(qū)域創(chuàng)建,如果區(qū)域不夠再嘗試使用 CAS+ 自旋鎖在其他位置劃分,失敗就再次嘗試,直到成功。
對象的訪問
Java 程序通過棧上的引用訪問堆中的對象。對象的訪問方式取決于 JVM 虛擬機上的實現(xiàn),目前主流的訪問方式是句柄和直接指針。
句柄
句柄相當于一個中間表,存儲著對應實例對象的地址以及實例數(shù)據所對應類信息的地址。
優(yōu)勢:比較穩(wěn)定,當對象被移動后(垃圾回收時移動對象是非常常見的事)時只需要改變句柄中的指針就可以了。句柄本身不需要改變。
直接指針
引用直接指向實例對象,在對象上保存對應的類信息所在的地址。
優(yōu)勢:查找快,在棧上的引用可以很快找到對應的對象。這也是 HotSpot 默認的訪問方式。
以上就是詳解jvm對象的創(chuàng)建和分配的詳細內容,更多關于jvm對象的創(chuàng)建和分配的資料請關注腳本之家其它相關文章!
相關文章
SpringBoot中實現(xiàn)數(shù)據字典的示例代碼
這篇文章主要介紹了SpringBoot中實現(xiàn)數(shù)據字典的示例代碼,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-09-09java.sql.SQLRecoverableException關閉的連接異常問題及解決辦法
當數(shù)據庫連接池中的連接被創(chuàng)建而長時間不使用的情況下,該連接會自動回收并失效,就導致客戶端程序報“ java.sql.SQLException: Io 異常: Connection reset” 或“java.sql.SQLException 關閉的連接”異常問題,下面給大家分享解決方案,一起看看吧2024-03-03用3個實例從原理到實戰(zhàn)講清楚Log4j史詩級漏洞
最近應該很多人都在關注著一個漏洞Apache Log4j 2遠程代碼執(zhí)行,該漏洞一旦被攻擊者利用會造成嚴重危害,這篇文章主要給大家介紹了關于如何用3個實例從原理到實戰(zhàn)講清楚Log4j史詩級漏洞的相關資料,需要的朋友可以參考下2021-12-12