JVM體系結構概述

JVM體系結構概覽

JVM的位置

在說JVM體系結構之前先來看看JVM的位置。

JVM是運行在操作系統之上的,與硬件沒有直接的交互,但是可以調用底層的硬件,用JIN(Java本地接口調用底層硬件)

JVM體系結構

類裝載器(Class Loader)

概念

負責加載class文件,將class文件字節碼加載到內存中,並將這些內容轉換成方法區中的運行時數據結構。類加載器只負責class文件的加載,至於他是否可以運行,由Excution Engine決定。  

解釋:

Car.class是由.java文件編譯得來的.class文件,存在本地磁盤。

ClassLoader:類加載器,負責加載並初始化 類文件,得到真正的Class類,即模板。

Car Class:由Car.class字節碼文件,通過ClassLoader加載並且初始化得到,這個Car就是當前類的模板,這個Car Class模板就存在方法區。

car1,car2,car3:是由Car模板經過實例化而得,一個模板可以獲得多個實例化對象。

拿car1舉例,car1.getClass()可以得到模板Car類,Car.getClassLoader()可得到其裝載器。

種類

一共有四類。

虛擬機自帶的加載器:

  • 啓動類加載器,也叫根加載器(BootStrap)。由C++編寫,程序中自帶的類,存儲在 $JAVAHOME/jre/lib/rt.jar中,如object類等

  • 擴展類加載器(Extension),Java編寫,在我們看到的類路徑中,凡是以Javax開頭的,都是拓展包,存儲在 $JAVAHOME/jre/lib/ext/*.jar 中

  • 應用程序類加載器(AppClassLoader),即平時程序中自定義的類 new出來的

用戶自定義的加載器:    

    Java.lang.ClassLoader的子類,用戶可以定製類的加載方式,即如果你的程序有特殊的需求,你也可以自定義你的類加載器的加載方式 ,進入ClassLoader的源碼,其爲抽象類,因此在你定製化開發的時候,需要你定義自己的加載器類來繼承ClassLoader抽象類即可,即 MyClassLoader extends ClassLoader

Java的類加載機制,永遠是以 根加載器->拓展類加載器->應用程序類加載器 這樣的一個順序進行加載的。如下圖:

雙親委派機制

概念:當一個類收到類加載請求後,他不會首先去加載這個類,而是把這個請求委派給父類去完成。每一個層次的類加載器都是如此,因此所有的類加載器請求都是應該傳到根加載器中的,只有當其父類加載器自己無法完成這個請求的時候(在他的加載路徑下沒有找到所需加載的class),子類加載器纔會嘗試自己去加載。

舉個例子:

有一個類A.java,當要使用A類時,類加載器要先去根加載器(BootStrap)中去找,如果找到就使用根加載器中的A類,不繼續往下執行,但是如果找不到,則依次下放,去拓展類加載器中找,同理找到就用,找不到繼續下放,再去應用程序類加載器中找,找到就用,找不到就報classNotFound Exception的異常。

採用雙親委派機制的好處就是不管是哪個加載器加載這個類,最終都是委託給頂層的啓動類加載器進行加載,這樣就保證了使用不同的類加載器最終得到的都是同一個Object對象。

沙箱安全機制

通過雙親委派機制,類的加載永遠都是從根加載器開始,依次下放,保證你所寫的代碼不會污染Java自帶的源代碼,保證了沙箱的安全。

本地接口Native Interface

    本地接口的作用是融合不同的編程語言爲 Java 所用,它的初衷是融合 C/C++程序,Java 誕生的時候是 C/C++橫行的時候,要想立足,必須有調用 C/C++程序,於是就在內存中專門開闢了一塊區域處理標記爲native的代碼,它的具體做法是 Native Method Stack中登記 native方法,在Execution Engine 執行時加載native libraies。

     目前該方法使用的越來越少了,除非是與硬件有關的應用,比如通過Java程序驅動打印機或者Java系統管理生產設備,在企業級應用中已經比較少見。因爲現在的異構領域間的通信很發達,比如可以使用 Socket通信,也可以使用Web Service等等,不多做介紹。

本地方法棧Native Method Stack

它的具體做法是Native Method Stack中登記native方法,在Execution Engine 執行時加載本地方法庫

程序計數器(PC寄存器)

每個線程都有一個程序計數器,是線程私有的,就是一個指針,指向方法區中的方法字節碼(用來存儲指向下一條指令的地址,也即將要執行的指令代碼),由執行引擎讀取下一條指令,是一個非常小的內存空間,幾乎可以忽略不記。

方法區

方法區是被所有線程共享,所有字段和方法字節碼,以及一些特殊方法如構造函數,接口代碼也在此定義。簡單說,所有定義的方法的信息都保存在該區域,此區屬於共享區間。

靜態變量+常量+類信息(構造方法/接口定義)+運行時常量池 存在方法區中

但是  實例變量存在堆內存中,和方法區無關

棧中的數據都是以棧幀(Stack Frame)的格式存在,棧幀就是一個內存區塊,是一個有關方法(Method)和運行期數據的數據集,當一個方法A被調用時就產生了一個棧幀F1,並被壓入到佔中,

A方法又調用了 B方法,於是產生棧幀 F2 也被壓入棧,

B方法又調用了 C方法,於是產生棧幀 F3 也被壓入棧,

……

執行完畢後,先彈出F3棧幀,再彈出F2棧幀,再彈出F1棧幀……

遵循“先進後出”/“後進先出”原則。

堆、棧、方法區三者的關係

HotSpot是使用指針的方式來訪問對象:

Java堆中會存放訪問類元數據的地址,

reference存儲的就直接是對象的地址。

體系結構

一個JVM實例只存在一個堆內存,堆內存的大小是可以調節的。類加載器讀取了類文件後,需要把類、方法、常變量放到堆內存中,保存所有的引用類型的真是信息,以方便執行器執行。堆內存分爲三部分:

  • Young Generation Space    新生區     Young/New 

  • Tenure Generation Space    老年區     Old/Tenure

  • Permanent Space                 永久區      Perm

堆內存邏輯上分爲三部分:新生+老年+永久

實際上(物理上)堆內存的結構是: (永久區的內存區域並沒有和堆其他區域在一塊連續的內存空間中)

 

新生代(Young Generation Space)

新生區是類的誕生、成長、消亡的區域,一個類在這裏產生,應用,最後被垃圾回收器收集,結束生命。新生區又分爲兩部分: 伊甸區(Eden space)和倖存者區(Survivor pace) ,所有的類都是在伊甸區被new出來的。倖存區有兩個: 0區(Survivor 0 space)和1區(Survivor 1 space)。當伊甸園的空間用完時,程序又需要創建對象,JVM的垃圾回收器將對伊甸園區進行垃圾回收(Minor GC),將伊甸園區中的不再被其他對象所引用的對象進行銷燬。然後將伊甸園中的剩餘對象移動到倖存 0區。若倖存 0區也滿了,再對該區進行垃圾回收,然後移動到 1 區。那如果1 區也滿了呢?再移動到養老區。若養老區也滿了,那麼這個時候將產生MajorGC(FullGC),進行養老區的內存清理。若養老區執行了Full GC之後發現依然無法進行對象的保存,就會產生OOM異常“OutOfMemoryError”。

如果出現java.lang.OutOfMemoryError: Java heap space異常,說明Java虛擬機的堆內存不夠。原因有二:

(1)Java虛擬機的堆內存設置不夠,可以通過參數-Xms、-Xmx來調整。

(2)代碼中創建了大量大對象,並且長時間不能被垃圾收集器收集(存在被引用)

永久代(Permanent Space )

永久存儲區是一個常駐內存區域,用於存放JDK自身所攜帶的 Class,Interface 的元數據,也就是說它存儲的是運行環境必須的類信息,被裝載進此區域的數據是不會被垃圾回收器回收掉的,關閉 JVM 纔會釋放此區域所佔用的內存。

永久代在各個版本中的發展:

Jdk1.6及之前: 有永久代, 常量池1.6在方法區

Jdk1.7: 有永久代,但已經逐步“去永久代”,常量池1.7在堆

Jdk1.8及之後: 無永久代,常量池1.8在元空間

在永久代中可能發生的程序異常:

        如果出現java.lang.OutOfMemoryError: PermGen space,說明是Java虛擬機對永久代Perm內存設置不夠。一般出現這種情況,都是程序啓動需要加載大量的第三方jar包。例如:在一個Tomcat下部署了太多的應用。或者大量動態反射生成的類不斷被加載,最終導致Perm區被佔滿。

在JDK1.8版本廢棄了永久代,替代的是元空間(MetaSpace),元空間與永久代上類似,都是方法區的實現,他們最大區別是:元空間並不在JVM中,而是使用本地內存。

元空間有注意有兩個參數:

  • MetaspaceSize :初始化元空間大小,控制發生GC閾值

  • MaxMetaspaceSize : 限制元空間大小上限,防止異常佔用過多物理內存

爲什麼移除永久代?

移除永久代原因:爲融合HotSpot JVM與JRockit VM(新JVM技術)而做出的改變,因爲JRockit沒有永久代。

有了元空間就不再會出現永久代OOM問題了!

分代概念

新生成的對象首先放到年輕代Eden區,當Eden空間滿了,觸發Minor GC,存活下來的對象移動到Survivor0區,Survivor0區滿後觸發執行Minor GC,Survivor0區存活對象移動到Suvivor1區,這樣保證了一段時間內總有一個survivor區爲空。經過多次Minor GC仍然存活的對象移動到老年代。

老年代存儲長期存活的對象,佔滿時會觸發Major GC=Full GC,GC期間會停止所有線程等待GC完成,所以對響應要求高的應用盡量減少發生Major GC,避免響應超時。

Minor GC : 清理年輕代 

Major GC : 清理老年代

Full GC : 清理整個堆空間,包括年輕代和永久代

所有GC都會停止應用所有線程。

爲什麼分代?

將對象根據存活概率進行分類,對存活時間長的對象,放到固定區,從而減少掃描垃圾時間及GC頻率。針對分類進行不同的垃圾回收算法,對算法揚長避短

爲什麼倖存區分爲兩塊相等大小的空間?

主要爲了解決碎片化。如果內存碎片化嚴重,也就是兩個對象佔用不連續的內存,已有的連續內存不夠新對象存放,就會觸發GC。

 

垃圾回收及其算法:https://blog.csdn.net/wzh66888/article/details/104735469

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章