JVM內存模型和類加載機制

JVM內存模型

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

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

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

線程共享區:數組

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

Java堆(Heap):Java堆也是線程共享區域,類的實例存放在這裏,一個系統會產生不少Java實例,所以Java堆的空間是最大的,若是Java堆的空間不足,就會拋出OutOfMemoryError異常。框架

線程私有區:jvm

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

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

程序計數器:這裏對應的類以及加載,實例對象,方法,靜態變量去了該去的地方,那麼問題來了,程序該怎麼執行,哪一個方法先執行,哪一個方法後執行,這些指令執行的順序就是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

相關文章
相關標籤/搜索