5.Java虛擬機運行原理


JVM(Java 虛擬機)在運行Java程序的時候,有點類似於即時編譯系統。每一個Java程序都是從main主函數開始運行的,JVM則負責將它從代碼編譯運行成為一個程序。同時,JVM是JRE(Java Runtime Environment)的一個組成部分。

Java程序最大的一個特性便是“一次編寫,隨處運行”,這意味着你可以將自己編寫的Java代碼無需經過任何調整,就可以在任何支持Java的平台上運行。而這樣的機制則離不開JVM的特性。

當我們將一個.java的文件進行編譯,編譯程序會生成一個相同名字而后綴為.class的文件。當我們試圖運行這個.class文件的時候,JVM需要經過一系列的步驟才能將它運行起來,這些步驟基本上包含了JVM的所有特性。

raw picture
在整個過程中,JVM主要負責三件事

  • 加載
  • 鏈接
  • 初始化

1.加載:

首先由類加載器(Class Loader)讀取.class文件中的內容,然后生成相應的二進制數據並且將其保存在方法區(Method Area)之中。對於每一個.class文件來說,JVM將以下些信息進入方法區:

  • 被加載類的全名以及它的直接父類
  • 該.class類是否與其他的類、接口或者枚舉相關聯
  • 反射器、變量以及方法信息等

將.class文件加載完成后,JVM將會創建一個Class類型的類對象,然后將它放置進入堆內存(Heap)中。值得注意的是,這個對象跟我們在代碼中的對象是沒有關系的,它是由Java虛擬機自動生成的,它的類型定義在java.lang這個包中。我們在編寫代碼時可以使用getClass()方法來獲取這個對象,然后通過這個類對象來獲取類級別的信息,比如說類的名稱、類的父類,方法和變量等等。例如:

import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class Main {

    // 主函數,這里是程序的入口
    public static void main(String[] args) {
        // 創建一個Student對象,它的名稱是s1
        Student s1 = new Student();
        // 獲取s1這個對象的類對象
        Class c1 = s1.getClass();
        // 將c1的名稱打印出來
        System.out.println(c1.getName());
        // 獲取c1這個類對象中所有的方法,存入數組中
        Method m[] = c1.getDeclaredMethods();
        for (Method method : m)
            System.out.println(method.getName());
        // 獲取c1這個類對象中的所有成員變量,存入數組中
        Field f[] = c1.getDeclaredFields();
        for (Field field : f)
            System.out.println(field.getName());
    }
}

// Student類的定義
public class Student {
    private String name;
    private int roll_No;

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public int getRoll_no() { return roll_No; }
    public void setRoll_no(int roll_no) { this.roll_No = roll_no; }
}

輸出為:

Student
getName
setName
setRoll_no
getRoll_no
name
roll_No

提示:對於每一個類來說,不過你new出多少個對象,都只有一個Class類型的類對象。每一個由該類創建出來的對象都指向同一個類對象的引用。例如:

Student s1 = new Student();
Class c1 = s1.getClass();

Student s2 = new Student();
Class c2 = s2.getClass();

System.out.println(c1==c2); // 結果為true

2.鏈接:

這一步主要進行執行驗證、執行准備、以及(可能的)解決問題

  • 驗證:這一步主要是驗證.class文件的正確性。比如說驗證這個文件是否被編譯器正確地編譯了,因為編譯器也可能會出錯的。如果這一步失敗,將會拋出一個java.lang.VerifyError類型的運行時(run-time)異常。
  • 執行准備: JVM為變量分配存儲空間以及對變量進行初始化。
  • 解決問題: 這一步主要是將引用的變量轉換為它的實際類型,這個實際類型是存儲在方法區(Method Area)中的。

3.初始化

在這一階段,所有靜態成員變量都將會被賦值(直接賦值或者通過static代碼塊賦值)。在多個類中,這一步是父類到子類依次執行;在一個類中,這一步從上至下執行。
一般來說,總共會有3個類加載器:

  • 引導類加載器(Bootstrap class loader):正因為JVM具有引導類加載器,才能夠正確地加載所要運行的類。它會將%JAVA_HOME%/jre/lib這個文件夾中的類先加載進來。這個文件夾也可以為引導文件夾,其中的內容是以C或者C++來實現的。
  • 拓展類加載器(Extension class loader):是引導類加載器的一個子類,它主要負責加載%JAVA_HOME%/jre/lib/ext這個文件夾中的內容。它由sun.misc.Launcher$ExtClassLoader這個類實現。
  • 系統/應用類加載器(System/Application class loader):是拓展類加載器的子類,負責加載程序員所編寫的類。本質上它需要使用映射到java.class.path中的環境變量。它也是由sun.misc.Launcher$ExtClassLoader這個類實現。
public class Main {
    // 主函數,這里是程序的入口
    public static void main(String[] args) {
        // String類由引導類加載器來加載, 而引導類加載器不是Java對象,
        // 因此結果是null
        System.out.println(String.class.getClassLoader());

        // Main類由應用類加載器加載
        System.out.println(Main.class.getClassLoader());
    }
}

輸出為:

null
sun.misc.Launcher$AppClassLoader@18b4aac2

提示:JVM的設計遵從的是層級代理原則。應用類加載器負責加載擴展類加載器所委托的內容,而拓展類加載器負責加載引導類加載器所委托的內容。如果某一個類在%JAVA_HOME%/jre/lib這個文件夾可以找到,那么就加載它,否則會再次傳輸到拓展類加載器委托它來加載,如果拓展類加載器找不到就委托應用類加載器來加載。如若連應用加載器都找不到的話,就會拋出一個java.lang.ClassNotFoundException的運行時異常。
raw picture

JVM內存區

raw picture

  • 方法區(Method area):與類有關的內容比如說類的名稱、類的父類的名稱、變量和方法等待都會存儲在JVM內存的方法區中。JVM內存中只有一個方法區,所以里面的內容是可以共享的。
  • 堆內存(Heap area):關於對象的內容都會存儲在堆內存中,JVM內存區也是只有一個堆內存,所以它也是一個共享區。
  • 棧內存(Stack area):對於每一個線程來說,JVM都會在內存區中開辟一個棧內存。每一個棧內存塊中存儲着方法的調用,該方法的所有的局部變量都存儲在相應的幀中。當線程終止后,它所屬的棧內存也會被JVM所銷毀。由於每一個棧都是獨立的,所以這里面的資源無法被共享。
  • 程序計數器寄存器:全稱為Program Counter Registers,也叫PC Registers。里面存儲着線程當前執行指令的地址,每一個線程都會有對於的程序計數器寄存器。
  • 本地方法棧內存區(Native method stacks):每一個線程都會獨立創建一個本地方法堆棧,里面存儲的是本地庫(C、C++)方法程序信息。

執行引擎

執行引擎負責執行.class文件(也就是中間碼“byte-code”文件)。它通過逐行掃描讀取二進制數據,然后結合上面介紹的JVM內存區中的數據執行指令。執行引擎可以分成三個部分。

  • 解析器(Interpreter):逐行掃描二進制數據然后執行。解析器比較愣,如果有某一個方面被多次調用,它每次都會被重新解析。
  • 即時編譯器(Just-In-Time Compiler):即時編譯器也叫做JIT,解析器解析好的字節碼更改為本機的機器代碼。當解析器發現有方法被重復調用時,JIT直接為該段提供本地機器代碼,因此不用再重新解析,從而提高了解析器的效率。
  • 垃圾回收器(Garbage Collector):負責銷毀不再被引用的對象。

Java Native Interface (JNI) :

Java中native接口是執行引擎所需要的本地庫(C、C++)的集合。

本文原作者:Gaurav Miglani
鏈接:https://www.geeksforgeeks.org/jvm-works-jvm-architecture/


免責聲明!

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



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