JVM類加載概述

JVM運行時數據區概述
JVM垃圾回收概述java

JVM簡介

JVM是Java Virtual Machine(Java虛擬機)的縮寫,JVM是一種用於計算設備的規範,它是一個虛構出來的計算機,是經過在實際的計算機上仿真模擬各類計算機功能來實現的。Java虛擬機包括一套字節碼指令集、一組寄存器、一個棧、一個垃圾回收堆和一個存儲方法域。 JVM屏蔽了與具體操做系統平臺相關的信息,使Java程序只需生成在Java虛擬機上運行的目標代碼(字節碼),就能夠在多種平臺上不加修改地運行。JVM在執行字節碼時,實際上最終仍是把字節碼解釋成具體平臺上的機器指令執行。c++

  Java語言的一個很是重要的特色就是與平臺的無關性。而使用Java虛擬機是實現這一特色的關鍵。通常的高級語言若是要在不一樣的平臺上運行,至少須要編譯成不一樣的目標代碼。而引入Java語言虛擬機後,Java語言在不一樣平臺上運行時不須要從新編譯。Java語言使用Java虛擬機屏蔽了與具體平臺相關的信息,使得Java語言編譯程序只需生成在Java虛擬機上運行的目標代碼(字節碼),就能夠在多種平臺上不加修改地運行。Java虛擬機在執行字節碼時,把字節碼解釋成具體平臺上的機器指令執行。這就是Java的可以「一次編譯,處處運行」的緣由。算法

內存結構概述

類加載子系統(Class Loader)segmentfault

類加載器分爲:自定義類加載器 < 系統類加載器 < 擴展類加載器 < 引導類加載器api

類加載過程分爲:加載、連接、驗證、初始化。數組

程序計數器(Program Counter Register)安全

是一塊較小的內存空間,能夠看做是當前線程所執行字節碼的行號指示器,指向下一個將要執行的指令代碼,由執行引擎來讀取下一條指令。網絡

虛擬機棧 (Stack Area)數據結構

棧是線程私有,棧幀是棧的元素。每一個方法在執行時都會建立一個棧幀。棧幀中存儲了局部變量表、操做數棧、動態鏈接和方法出口等信息。每一個方法從調用到運行結束的過程,就對應着一個棧幀在棧中壓棧到出棧的過程。多線程

本地方法棧 (Native Method Area)

JVM 中的棧包括 Java 虛擬機棧和本地方法棧,二者的區別就是,Java 虛擬機棧爲 JVM 執行 Java 方法服務,本地方法棧則爲 JVM 使用到的 Native 方法服務。

堆 (Heap Area)

堆是Java虛擬機所管理的內存中最大的一塊存儲區域。堆內存被全部線程共享。主要存放使用new關鍵字建立的對象。全部對象實例以及數組都要在堆上分配。垃圾收集器就是根據GC算法,收集堆上對象所佔用的內存空間。

Java堆分爲年輕代(Young Generation)和老年代(Old Generation);年輕代又分爲伊甸園(Eden)和倖存區(Survivor區);倖存區又分爲From Survivor空間和 To Survivor空間。

方法區(Method Area)

方法區同 Java 堆同樣是被全部線程共享的區間,用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼。更具體的說,靜態變量+常量+類信息(版本、方法、字段等)+運行時常量池存在方法區中。常量池是方法區的一部分。

JDK 8 使用元空間 MetaSpace 代替方法區,元空間並不在JVM中,而是在本地內存中

類加載過程概述

類加載器子系統負責從文件系統或者網絡中在家Class文件,class文件在文件開頭又特定的文件標識。

ClassLoader只負責class文件的加載,至於它是否能夠運行,則由ExecutionEngine決定。

加載類的信息存放於一塊被稱爲方法區的內存空間。除了類的信息外,方法區中還會存放運行時常量池信息,可能還包括字符串字面量和數字常量(這部分常量信息是Class文件中常量池部分的內存映射)

類加載器ClassLoader角色

  1. class文件存在本地硬盤上,在執行時加載到JVM中,根據這個文件能夠實例化出n個如出一轍的實例。
  2. class文件加載到JVM中,被稱爲DNA元數據模板,放在方法區中。
  3. 在.class文件 -> JVM -> 最終成爲元數據模板的過程當中,ClassLoader就扮演一個快遞員的角色。

類的加載過程

類的加載過程大體分爲三個階段:加載,連接,初始化。

類的加載過程一:加載(Loading)

  1. 經過一個類的全限定名來獲取定義此類的二進制字節流
  2. 將這個字節流所表明的靜態存儲結構轉化爲方法區的運行時數據結構
  3. 在內存中生成一個表明這個類的java.lang.Class對象,做爲方法區這個類的各類數據的訪問入口

類的加載過程二:連接(Linking)

驗證(Verify)

  1. 目的在於確保Class文件的字節流中包含信息符合當前虛擬機要求,保證被加載類的正確性,不會危害到虛擬機的安全。

準備(Prepare)

準備階段是進行內存分配。爲類變量也就是類中由static修飾的變量分配內存,而且設置初始值,這裏要注意,初始值是默認初始值0、null、0.0、false等,而不是代碼中設置的具體值,代碼中設置的值是在初始化階段完成的。另外這裏也不包含用final修飾的靜態變量,由於final在編譯的時候就會分配了。這裏不會爲實例變量分配初始化,類變量會分配在方法區中,而實例對象會隨着對象一塊兒分配到Java堆中。

public class HelloApp {

    private static int a = 1; // 準備階段爲0,而不是1

    public static void main(String[] args) {
        System.out.println(a);
    }
}

解析(Resolve)

解析主要是解析字段、接口、方法。主要是將常量池中的符號引用替換爲直接引用的過程。直接引用就是直接指向目標的指針、相對偏移量等。

類的加載過程三:初始化(initialization)

  1. 初始化階段就是執行類構造器方法<clinit>()的過程
  2. 此方法不須要定義,是javac編譯期自動收集類中全部類變量的賦值動做和靜態代碼塊中的語句合併而來
  3. 構造器方法中指令按語句在源文件中出現的順序執行。
  4. <clinit>()不一樣於類的構造器(構造器是虛擬機視角下的<init>())
  5. 若該類具備父類,JVM會保證子類的<clinit>()執行前,父類的<clinit>()已經執行完畢
  6. 虛擬機必須保證一個類的<clinit>()方法在多線程下被同步加鎖

須要注意,若是沒有定義靜態變量或靜態代碼塊的話則沒有<clinit>()

案例以下:

public class HelloApp {


    static {
        code = 20;
    }
    private static int code = 10;
    
    //第一步:在準備階段初始化了code默認值爲0。
    //第二步:根據類執行順序先執行靜態代碼塊,賦值爲20.
    //第三步:最後賦值爲10,輸出結果爲10.
    
    public static void main(String[] args) {
        System.out.println(code); // 10
    }
}

經過字節碼文件能夠很清楚的看到結果:

0 bipush 20
 2 putstatic #3 <com/jvm/HelloApp.code>
 5 bipush 10
 7 putstatic #3 <com/jvm/HelloApp.code>
10 return

先被賦值爲20,而後改成10。

類加載器概述

JVM支持兩種類型的類加載器,分別爲引導類加載器(Bootstrap ClassLoader)自定義類加載器(User-Defined ClassLoader)

從概念上講,自定義類加載器通常指的是程序中由開發人員自定義的一類類加載器,可是Java虛擬機是將全部派生於抽象類ClassLoader的類加載器都劃分爲自定義類加載器

不管怎麼劃分,在程序中最多見的類加載器始終只有三個:

系統類加載器(System Class Loader) < 擴展類加載器(Extension Class Loader) < 引導類加載器(Bootstrap Class Loader)

它們之間的關係不是繼承關係,而是level關係。

系統類加載器和擴展類加載器間接或直接繼承ClassLoader。劃線分爲兩大類。

public class HelloApp {


    public static void main(String[] args) {

        //獲取系統類加載器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2

        //獲取其上層:擴展類加載器
        ClassLoader extClassLoader = systemClassLoader.getParent();
        System.out.println(extClassLoader);//sun.misc.Launcher$ExtClassLoader@60e53b93

        //獲取其上層:獲取不到引導類加載器
        ClassLoader bootStrapLoader = extClassLoader.getParent();
        System.out.println(bootStrapLoader);//null


        //咱們本身定義的類是由什麼類加載器加載的:使用系統類加載器
        ClassLoader classLoader = HelloApp.class.getClassLoader();
        System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2


        //看看String是由什麼類加載器加載的:使用引導類加載器
        ClassLoader classLoaderString = String.class.getClassLoader();
        System.out.println(classLoaderString);//null
    }

}

引導類加載器(Bootstrap ClassLoader)

  1. 這個類加載使用c/c++語言實現,嵌套在JVM內部。
  2. 他用來加載Java的核心庫,(JAVA_HOME/jre/lib/rt.jar、resources.jar、或sun.boot.class.path路徑下的內容),用於提供JVM自身須要的類。
  3. 並不繼承自java.lang.ClassLoader ,沒有父加載器。
  4. 加載擴展類和應用程序類加載器,並指定爲他們的父類加載器。
  5. 出於安全考慮,Bootstrap啓動類加載器只加載包名爲java、javax、sun等開頭的類。

擴展類加載器(Extension ClassLoader)

  1. Java語言編寫,由sun.misc.Launcher$ExtClassLoader實現。
  2. 派生於ClassLoader類。
  3. 上一層類加載器爲啓動類加載器。
  4. 從java.ext.dirs系統屬性所指定的目標中加載類庫,或從JDK的安裝目錄jre/lib/ext子目錄(擴展目錄)下加載類庫。若是用戶建立的Jar放在此目錄下,也會自動由擴展類加載器加載。

系統類加載器(System ClassLoader)

  1. Java語言編寫,由sun.misc.Launcher$AppClassLoader實現。
  2. 派生於ClassLoader類。
  3. 上一層類加載器爲擴展類加載器。
  4. 它負責加載環境變量classpath或系統屬性 java.class.path指定下的類庫。
  5. 該類加載是程序中默認的類加載器,通常來講,Java應用的類都有由它來完成加載。
  6. 經過ClassLoader.getSystemClassLoader()方法能夠獲取該類加載器。

爲何須要用戶自定義類加載器?

  • 隔離加載類
  • 修改類加載的方式
  • 擴展加載源
  • 防止源碼泄露

用戶自定義類加載器實現步驟

  1. 經過集成抽象類java.lang.ClassLoader類的方式,實現本身的類加載器。
  2. 在JDK1.2以前,在自定義類加載器時,總會去繼承ClassLoader類並重寫loadClass()方法,從而實現自定義的類加載器,可是在JDK1.2以後再也不建議用戶去覆蓋loadClass()方法,而是建議把自定義類加載邏輯寫在findClass()方法中。
  3. 在編寫自定義類加載器時,若是沒有太過於複雜的需求,能夠直接繼承URLClassLoader類,這樣就能夠避免本身編寫findClass()方法其獲取字節碼流的方式,使得自定義加載器編寫更爲簡潔。

關於ClassLoader

ClassLoader是一個抽象類,系統類加載器和擴展類加載器間接或直接繼承ClassLoader。

經常使用方法以下:

方法名稱 描述
getParent() 返回該類加載器的上一級類加載器
loadClass(String name) 加載名稱爲name的類,返回結果爲java.lang.Class的實例
findClass(String name) 查找名稱爲name的類,返回結果爲java.lang.Class的實例
findLoadedClass(String name) 查找名稱爲name的已經被加載過的類,返回結果爲java.lang.Class類的實例
defineClass(String name,byte[] b,int off,int len) 把字節數組b中的內容轉換爲一個java類,返回結果爲java.lang.Class類的實例
resolveClass(Class<?> c) 鏈接指定的一個java類

雙親委派機制

Java虛擬機對class文件採用的是按需加載的方式,也就是說須要使用該類的時候纔會將它的class文件加載到內存生成class對象。

當某個類加載器須要加載某個.class文件時,它首先把這個任務委託給他的上級類加載器,遞歸這個操做,若是上級的類加載器沒有加載,本身才會去加載這個類。

工做原理

  1. 若是一個類加載器收到了類加載請求,他並不會本身先去加載,而是把這個請求向上委託給上一級類加載器去執行。
  2. 若是上一級類加載器還存在上一級,則進一步向上委託,依次遞歸,請求最終會達到引導類加載器。
  3. 若是引導類加載器能夠完成類加載任務,就成功返回。若是沒法完成類加載任務,會依次往下再去執行加載任務。這就是雙親委派機制。

好比咱們如今在本身項目中建立一個包名java.lang下建立一個String類。

package java.lang;

public class String {
    
    static {
        System.out.println("我是本身建立的String");
    }
}
public class HelloApp {


    public static void main(String[] args) {

        String s = new String();

    }

}

執行以後並不會輸出咱們的語句,由於咱們的String類加載器一開始由系統類加載器一級一級往上委託,最終交給引導類加載器,引導類加載器一看是java.lang包下的,ok,我來執行,最終執行的並非咱們本身建立String類,保證了核心api沒法被纂改,避免類的重複加載。

package java.lang;

public class String {

    static {
        System.out.println("我是本身建立的String");
    }

    public static void main(String[] args) {
        System.out.println("Hello World !!!");
    }
}

若是咱們想運行如上代碼,咱們會獲得以下錯誤:

錯誤: 在類 java.lang.String 中找不到 main 方法, 請將 main 方法定義爲:
   public static void main(String[] args)
不然 JavaFX 應用程序類必須擴展javafx.application.Application

由於咱們知道在覈心api String類中是沒有main方法的,因此咱們能夠肯定加載的並非咱們本身建立的String類。

在JVM中表示兩個Class對象是否爲同一個類存在的必要條件:

  • 類的完整類名必須一致,包括包名。
  • 加載這個類的ClassLoader也必須相同。

順便說一句,咱們包名若是爲java.lang則會報錯。

相關文章
相關標籤/搜索