JVM內存模型和類加載機制


JVM內存模型

Java代碼是運行在Java虛擬機(JVM)上的,Java虛擬機通過解釋執行(解釋器)或編譯執行(編譯器)來完成。

Java內存模型分為5個部分:方法區(Method Area),Java堆(Heap),Java棧(VM Stack),本地方法棧(Native Method Stack),程序計數器(PC 寄存器)

(圖片來源:http://gityuan.com/images/jvm/jvm_memory_1.png)

線程共享區:

方法區(Method Area):方法區是各個線程共享的區域,存放類信息,常量,靜態常量,編譯器編譯后的代碼等信息。

Java堆(Heap):Java堆也是線程共享區域,類的實例存放在這里,一個系統會產生很多Java實例,因此Java堆的空間是最大的,如果Java堆的空間不足,就會拋出OutOfMemoryError異常。

線程私有區:

Java棧(VM Stack):線程私有區域,生命周期與線程相同,一個線程對應一個Java棧,每執行一個方法就會向棧里壓一個元素,這個元素叫“棧幀”,棧幀中包含了方法中保存了該方法調用的參數、局部變量和返回地址等信息,如果棧空間不足了就會拋出StackOverflowError異常。

本地方法棧(Native Method Stack):和Java棧類似,本地方法棧是用來執行本地方法的,存放的方法調用本地方法接口,最終調用本地方法庫,實現與操作系統,硬件交互的目的。

程序計數器:這里對應的類以及加載,實例對象,方法,靜態變量去了該去的地方,那么問題來了,程序該怎么執行,哪個方法先執行,哪個方法后執行,這些指令執行的順序就是PC寄存器在管,它的作用就是控制程序指令的執行順序。

類加載機制

 編寫的java代碼會通過編譯器編譯成字節編碼的.class文件,再把字節編碼加載到JVM中,映射到內存的各個區域中,程序就可以在內存中運行了。

類加載流程

(圖片來源:https://images2017.cnblogs.com/blog/352511/201708/352511-20170825174319746-900347526.png)

1,加載

加載是類裝載的第一步,內存中生成一個代表這個類的java.lang.class對象,通過class文件的路徑讀取到二進制流,並解析二進制里的元數據(類型,常量等),作為方法區這個類的各種數據量的入口;這里的不一定從class文件獲取,這里既可以從ZIP包(jar,war)包中獲取,也在運行時動態生成(jsp轉換成class文件,動態代理生成)。

2,連接

連接又可分為驗證,准備,解析。

2.1,驗證

驗證主要是判斷class文件的合法性,對版本號進行驗證(例如如果使用java1.8編譯后的class文件要再java1.6虛擬機上運行),還會對元數據,字節編碼等進行驗證,確保class文件里的字節流信息符合當前虛擬機的要求,不會危害虛擬機的安全。

2.2,准備

准備主要是分配內存,為變量分配初始值,即在方法區中分配這些變量所使用的內存空間,例如:

public static int i = 1;

在准備階段i的值會被初始化為0,后面的類的初始化階段才會賦值為1;

public static final int i = 1;

對應常量(static final)i,在准備階段就會被賦值1;

2.3,解析

解析就是把代碼中的符號引用替換為直接引用;例如某個類繼承了java.lang.Object,原來的符號引用記錄的是“java.lang.Object”,並不是java.lang,Object對象,直接引用就是找出對應的java.lang.Object對應的內存地址,建立直接引用關系;

3,初始化

初始化的過程包括執行類構造器方法,static變量賦值語句,static{}代碼塊,如果是一個子類進行初始化會先對其父類進行初始化,保證其父類在子類之前進行初始化;所以其實在java中初始化一個類,那么必然是先初始化java.lang.Object,因為所有的java類都繼承自java.lang.Object。

以下幾種情況不會執行類初始化:

  • 通過子類引用父類的靜態字段,只會觸發父類的初始化,而不會觸發子類的初始化。
  • 定義對象數組,不會觸發該類的初始化。
  • 常量在編譯期間會存入調用類的常量池中,本質上並沒有直接引用定義常量的類,不會觸發定義常量所在的類。
  • 通過類名獲取Class對象,不會觸發類的初始化。
  • 通過Class.forName加載指定類時,如果指定參數initialize為false時,也不會觸發類初始化,其實這個參數是告訴虛擬機,是否要對類進行初始化。
  • 通過ClassLoader默認的loadClass方法,也不會觸發初始化動作。

 類加載器

在JVM中有三中類加載器,BootStrap Classloader(啟動Classloader)、Extension Classloader(擴展Classloader)和APP Classloader(應用Classloader);

BootStrap ClassLoader主要加載JVM自身需要的類,這個加載器由C++編寫是虛擬機的一部分,負責加載 JAVA_HOME\lib 目錄中的,或通過-Xbootclasspath參數指定路徑中的,且被虛擬機認可(按文件名識別,如rt.jar)的類。

Extension Classloader是sun.misc.Launcher中的內部類ExtClassLoader,負責加載 JAVA_HOME\lib\ext 目錄中的,或通過java.ext.dirs系統變量指定路徑中的類庫。

APP ClassLoader是sun.misc.Launcher中的內部類AppClassLoader,負責加載用戶路徑上的類庫。

用戶也可以通過繼承ClassLoader實現自己的類加載器。

雙親委派模式

當一個類加載器接收到類加載的任務時,會首先交給其父類加載器去加載,只有當父類加載器無法加載是其才會自己加載。其好處是可以避免一個類被重復加載。

即使兩個類來源於相同的class文件,如果使用的類加載器不同,加載后的對象時完全不同的,這個不同反應在對象的 equals()、isAssignableFrom()、isInstance()等方法的返回結果,也包括了使用 instanceof 關鍵字對對象所屬關系的判定結果。

雙親委派模式的問題

頂層ClassLoader,無法加載底層ClassLoader的類

Java框架(rt.jar)如何加載應用的類?

比如:javax.xml.parsers包中定義了xml解析的類接口
Service Provider Interface SPI 位於rt.jar 
即接口在啟動ClassLoader中。
而SPI的實現類,在AppLoader。

這樣就無法用BootstrapClassLoader去加載SPI的實現類。

解決

JDK中提供了一個方法:

   1:  Thread. setContextClassLoader()

用以解決頂層ClassLoader無法訪問底層ClassLoader的類的問題;
基本思想是,在頂層ClassLoader中,傳入底層ClassLoader的實例。

 

Reference:http://www.cnblogs.com/leefreeman/p/7429112.htmlhttp://www.importnew.com/25295.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM