Java虛擬機內存模型

Java虛擬機內存模型

內存結構篇

一、內存結構簡介

image

  • 程序計數器java

    • 當前線程所執行的字節碼行號指示器;
    • 分支、循環、跳轉等控制;
    • 當執行的是java方法時是正在執行的虛擬機字節碼指令的地址;
    • 當執行的是Native(JNI)方法時該指針爲空;
    • 沒有(out of memory error)OOM。
  • 數據庫

    • 生命週期與線程相同;
    • 每一個方法執行時都會建立一個棧幀(stack frame);
    • 用於存儲局部變量表、操做數棧、動態連接、方法出口等信息;
    • 每一個方法從調用到執行成的過程對應棧幀在棧中入棧到出棧的過程;
    • 棧深過大會StackOverFlowError;內存不足會OOM;
  • 本地方法棧數組

    • Native method stack,跟棧同樣,棧服務於java方法,本地方法棧服務於native 方法(JNI),部分虛擬機是合併的。
  • 緩存

    • 因此線程共享;
    • 存放對象實例;
    • 垃圾回收的主要區域;
    • 物理內存上能夠不連續;
    • 會有OOM問題;
  • 方法區安全

    • 線程共享;
    • 用於存儲已經被虛擬機加載的類信息,常量、靜態變量等;
    • 類的加載、卸載、常量池回收均發生在此;
    • 內存不足會有OOM ;
    • 運行時常量池:方法區一部分,存放編譯時期生成的各類字面量和符號引用,具備動態特性,內存不足也會OOM;**運行時常量池時放在方法區(1.8更名放在元空間)
  • 直接內存多線程

    • 與JVM定義內存區域無關,不歸JVM管理;
    • 內存不足也會OOM;
    • native庫直接分配的堆外內存;不會回收。例如Netty緩衝區,不須要回收,能夠反覆用。

二、棧和棧幀

棧幀:一個方法對應一個棧幀區域,先進後出FILO,壓棧再出棧。運維

操做數棧:是各類運算髮生的場所,各類數據在進行運算時都會彈入操做數棧,而後結果會彈出操做數棧。jvm

方法出口:存儲的是當前方法執行完畢以後應該返回到上一級方法的位置。佈局

image

三、堆內存邏輯分區

image

四、元空間

方法區又稱永久代在1.8稱爲元空間。性能

元空間替換永久代緣由

​ 一、Java7及之前版本的Hotspot中方法區位於永久代中。同時,永久代和堆是相互隔離的,但它們使用的物理內存是連續的。永久代的垃圾收集是和老年代捆綁在一塊兒的,所以不管誰滿了,都會觸發永久代和老年代的垃圾收集。

​ 二、元空間存在於本地內存,意味着只要本地內存足夠,它不會出現像永久代中「java.lang.OutOfMemoryError: PermGen space」這種錯誤。看上圖中的方法區,是否是「膨脹」了。默認狀況下元空間是能夠無限使用本地內存的,但爲了避免讓它如此膨脹,JVM一樣提供了參數來限制它使用的使用。

​ 表面上看是爲了不OOM異常。由於一般使用PermSize和MaxPermSize設置永久代的大小就決定了永久代的上限,可是不是總能知道應該設置爲多大合適, 若是使用默認值很容易遇到OOM錯誤。當使用元空間時,能夠加載多少類的元數據就再也不由MaxPermSize控制, 而由系統的實際可用空間來控制。

​ 更深層的緣由仍是要合併HotSpot和JRockit的代碼,JRockit歷來沒有所謂的永久代,也不須要開發運維人員設置永久代的大小,可是運行良好。同時也不用擔憂運行性能問題了,在覆蓋到的測試中, 程序啓動和運行速度下降不超過1%,可是這點性能損失換來了更大的安全保障。

總結:

  • 永久代:GC不會再程序運行期間對永久代進行垃圾回收,這會致使OOM。
  • 元數據空間:不存在虛擬機中,而是使用本地內存,大小由系統實際可用內存控制。
元空間配置參數
-XX:MetaspaceSize,class metadata的初始空間配額,以bytes爲單位,達到該值就會觸發垃圾收集進行類型卸載,同時GC會對該值進行調整:若是釋放了大量的空間,就適當的下降該值;若是釋放了不多的空間,那麼在不超過MaxMetaspaceSize(若是設置了的話),適當的提升該值。

-XX:MaxMetaspaceSize,能夠爲metadata分配的最大空間,默認是沒有限制的。

-XX:MinMetaspaceFreeRatio,在GC以後,最小的Metaspace剩餘空間容量的百分比。

-XX:MaxMetaspaceFreeRatio,在GC以後,最大的Metaspace剩餘空間容量的百分比。

五、常量池

常量池

​ Class能夠理解爲Class文件的資源倉庫.class文件中除了包含類的版本、字段、方法、接口等描述信息,還有一項就是常量池,常量池中用於存放編譯期間生成的各類字面變量和符號引用。

​ 八中基本類型中byte、short、integer、long、char等在值小於等於127使用對象池,即不負責建立和管理大於127的對象。

字符串常量池
  1. 字符串的分配和其餘的對象分配同樣,耗費高昂的時間和空間代價,做爲基礎數據,大量建立字符串影響程序性能。
  2. JVM爲了提升性能和減小開銷,在實例化字符串常量時進行了優化。
  • 爲字符串開闢一個字符串常量池,相似緩存區。
  • 建立字符串常量是,先檢查字符串常量池是否存在該字符串。
  • 存在則返回該字符串的引用,不存在時則實例化該字符串並放入池中(String s=new String("abc");這種方式會新建出字符串,與常量池的不一樣)。

例子:

String s1 = "abc"; String s2 = "abc"; String s3 = new String("abc"); String s4 = new String("abc"); s1==s2    s2!=s3    s3!=s4

例題1:String str=new String("abc");建立了多少個對象?

答:建立過程以下:

  1. 在常量池查找"abc"對象,有則返回對應的引用實例,沒有則在常量池建立對應實例對象。
  2. 在堆中new一個String("abc")對象。
  3. 將對象地址賦值給str,建立一個引用。

所以,常量池沒有"abc"字面量則建立兩個對象,不然建立一個對象以及建立一個對象的引用。

例題2:String str=new String("a"+"b");建立了多少個對象?

答:字符串常量池:a、b、ab。

​ 堆:new String("ab")。

​ 引用:str。

合計5個。

六、TLAB

​ TLAB(Thread Local Allocation Buffer)線程本地分配緩衝區。

​ JVM分配對象是優先分配到線程棧上,棧上分配不了的(如對象較大)則直接分配在Old區;若是對象不大,優先分配在棧上的TLAB上。

​ TLAB是在Eden區的專門的內存空間,爲了防止在Eden區分配空間的多線程競爭資源,JVM爲每一個線程在Eden區上分配的專屬內存空間即TLAB。

內存分配和對象佈局篇

一、JOL對象內存佈局

Java Object Layout 對象的內存佈局:即對象在內存中如何分佈的。

image

數組對象:markword(8) + classPointer(4) + 數組長度(4) + 實例數據 + 對齊

Ps:壓縮指針和壓縮普通對象指針:

使用java -XX:+PrintCommandLineFlags -version命令能夠看到包含如下信息:

顯示: -XX:+UseCompressedClassPointers -XX:+UseCompressedOops,

其中-XX:+UseCompressedClassPointers是使用壓縮類指針,原先是8字節,因爲8字節=8x8=64位,2^64位尋址空間太大,所以不必使用8字節,所以使用了壓縮成4字節的壓縮指針,4字節的尋址能力:48=32位,2^32=4G(2^10=1024=1KB 2^20=1M 2^30=1G) 又因爲JVM是8字節一尋址,也就是每8字節做爲一個單位,所以實際尋址能力4G8=32G,ZGC號稱最大可以使用4T的內存,ZGC使用8字節做爲ClassPointer 其中有42位爲類指針,4位爲顏色指針,2^42=4T。

其中-XX:+UseCompressedOops是表示使用壓縮對象指針進行尋址,兩個指令應該是一對的。

關閉壓縮指針-XX:-UseCompressedClassPointers -XX:-UseCompressedOops指令。

二、爲新對象分配內存的方式

  • 內存規整:直接移動指針到未被使用的區域(指針碰撞),須要帶壓縮的GC:Serial、ParNew等帶compact的垃圾回收器。
  • 內存不規整:空閒列表維護可用空間,例如:CMS等基於mark-sweep的垃圾回收器。

三、內存分配的線程安全

​ CAS保證,每一個線程在jvm預先分配了內存,稱爲本地線程分配緩衝區TLAB,並同步鎖定。

四、對象的訪問定位

​ 棧上的reference數據操做具體堆上的對象有句柄和直接指針兩種方式。

句柄方式:jvm堆上劃份內存來做爲句柄池,引用時引用存儲對象的句柄地址,句柄中包含對象實例數據和類型數據的各自具體地址信息。

​ 優勢:引用不用修改,比較穩定,對象移動如垃圾回收只要改變句柄值便可。

image

直接指針:引用直接引用對象地址,對象中包含類型數據指針,指向方法區class。

​ 優勢:速度快。

image

五、對象的建立過程

  • new 指令,開闢空間(申請、初始化),這裏的初始化是半初始化,仍是默認值,例如對象中有變量 int i=8; 此時初始化後i=0 即默認值,因此稱爲半初始化。
  • invokespecial ,構造方法,賦值(真正的初始化,i=8)。
  • astore, 對象創建關聯,棧空間與堆空間創建關聯。
  • 初次訪問對象

    其中二、3兩部會發生指令重排。

六、對象逃逸

JVM的3中運行模式:

解釋模式:只使用解釋器,執行一行JVM字節碼就編譯一行爲機器碼。這樣能更快的看到程序的執行效果,可是執行的效果並不必定最快。適合執行一次的代碼模塊。

編譯模式:只使用編譯器,先將全部的JVM字節碼一次編譯爲機器碼,而後一次性執行全部機器碼。這樣啓動的稍慢,但執行完很快,適合反覆執行的代碼模塊;

混合模式:依然採用解釋模式,可是對於一點熱點代碼採用編譯模式,並把對應的字節碼緩存起來。JVM通常採用混合模式。

對象逃逸分析:

public User t1(){
    User user = new User();
    user.setId(1);
    user.setName("sz");
    //寫入數據庫
    return user;
}

public User t2(){
    User user = new User();
    user.setId(1);
    user.setName("sz");
    //寫入數據庫
}

t1對象會被其餘引用,做用範圍不明顯;t2對象不會被其餘線程引用,直接分配到當前線程。

對象逃逸分析的JVM參數: -XX:+DoEscapeAnalysis(開啓)-XX:DoEscapeAnalysis(關閉)。

JDK1.7以後默認開啓,但若是棧空間不足會分配到堆空間。逃逸分析發生在編譯期間。

相關文章
相關標籤/搜索