Java程序執行過程及內存機制

本講將介紹Java代碼是如何一步步運行起來的,其中涉及的編譯器,類加載器,字節碼校驗器,解釋器和JIT編譯器在整個過程當中是發揮着怎樣的做用。此外還會介紹Java程序所佔用的內存是被如何管理的:堆、棧和方法區都各自負責存儲哪些內容。最後用一小塊代碼示例來幫助理解Java程序運行時內存的變化。java

Java程序執行過程

  • 步驟 1: 寫源代碼,源代碼將以.java的文件格式保存在電腦硬盤中。
  • 步驟 2: 編譯器(compiler)檢查是否存在編譯期錯誤(例如缺乏分號,關鍵字拼寫錯誤等)。若經過檢測,編譯器就會將源代碼翻譯成字節碼(bytecode),以.class的文件格式保存在電腦硬盤中。
  • 步驟 3: 若要運行此Java程序,JVM中會有一個叫類加載器(class loader)的內置程序把字節碼從硬盤載入到正位於內存中的JVM裏去。
  • 步驟 4: JVM中還有一個叫字節碼校驗器(bytecode verifier)的內置程序檢測是否存在運行期錯誤(例如棧溢出)。若經過檢測,字節碼校驗器就會將字節碼傳遞給解釋器(interpreter)。
  • 步驟 5: 解釋器會對字節碼進行逐行翻譯,將其翻譯成當前所在系統能夠理解的機器碼(machine code),
  • 步驟 6:將機器碼交給操做系統,操做系統會以main方法做爲入口開始執行程序。至此,一個Java程序就這樣運行起來了。

細心的讀者可能注意到了,在流程圖中還涉及到一個叫JIT的東西在步驟中沒有被解釋。那麼JIT編譯器(Just-In-Time Compiler)是若是參與進程序的執行過程當中呢?讓咱們來看如下兩個例子。緩存

  • 狀況 1: 解釋器對代碼進行逐行解釋,正如咱們在步驟中所介紹的。
  • 狀況 2: 這是JIT編譯器參與進Java執行過程的狀況,JIT編譯器會掃描全部代碼並對其進行優化。例如此時它發現最後一行代碼是重複多餘的,就會將其移除,只傳遞前4行代碼給解釋器。這樣解釋器就能運行地更快速高效,畢竟少了一行多餘的代碼須要翻譯。

固然,這只是JIT編譯器的優化手段之一,不一樣公司設計的JIT編譯器對Java程序的運行會有不一樣的優化方式。此外須要知道的是,JIT編譯器並非每次都會參與到執行過程當中來。jvm

內存機制

在步驟3中咱們談到字節碼會被類加載器載入到內存,那麼載入以後JVM是如何對其進行內存管理的呢?優化

一般,在載入內存後,一個Java程序所佔用的內存會被大體分爲3塊區域:堆(heap),棧(stack)和方法區(method area)。操作系統

堆:存放new出來的東西。.net

棧:存放局部變量。線程

方法區:類型信息,字段信息,常量池(constant pool),靜態變量,方法信息等。翻譯

public final class Student extends Object implements Serializable { // 1.類信息
    // 2.對象字段信息
    private String name;
    private int score;
 
    // 3.常量池
    public final int id = 0;
    public final String gender = "male";
  
  	// 4.靜態變量
  	public static int a = 0;
    
    // 5.方法信息
    public int getid() {
        return id;
    }
}

PC寄存器:存放將要執行的指令的地址。(由於機器的腦子不靈活,因此須要一塊專門的區域幫他記住執行到哪一步,否則它會忘記)設計

本地方法棧:與JVM棧所發揮的做用是很是類似的,其區別不過是JVM棧爲Java方法服務,而本地方法棧則是爲使用到的Native方法服務。有的虛擬機(例如Sun HotSpot虛擬機)甚至直接就把本地方法棧和虛擬機棧合二爲一。code

每一個線程擁有各自獨立的(虛擬機)棧、PC寄存器和本地方法棧。而堆和方法區則是全部線程共享的。

最後讓咱們經過一個小例子來理解Java程序執行時內存的變化。

public class Person {
  int id;
  int age;
  
  Person(int id1, int age1) {
    id = id1;
    age = age1;
  }
  
  public static void main(String[] args) {
		Person Tom = new Person(1, 25);
  }
}

首先,在stack中申請了一塊內存,這塊內存區域名字叫Tom,此時區域裏存儲的內容爲null。

接着,調用Person的構造方法,方法的參數屬於局部變量,所以在stack中有兩塊區域分別存放id1和age1。

經過構造方法,能夠new出來一個Person的對象,這個對象連帶着其成員變量會被存放在heap中。成員變量id和age的值由存放在stack中的局部變量id1和age1賦予。

最後,將這個對象的引用值(相似於地址)傳遞給Tom,經過引用值咱們就能夠找到這個對象。

(注意:位於stack中的id1和age1會隨着構造方法調用的結束而消失,這裏爲了更好地表現全過程,所以保留在圖中。)

關於棧和堆的其餘小知識

  • 棧和堆的大小都是固定爲一個默認值的,它們做爲jvm的參數設定好了,不一樣的jvm設定的參數不一樣,相應的棧和堆的大小也就不一樣。
  • 棧是運行時的單位:裏面存儲的信息都是跟當前線程相關的,包括局部變量、程序運行狀態、方法返回值等;而堆是存儲的單位:它只負責存儲對象。
  • 當一個方法調用結束後,方法裏的局部變量會隨之消失,不會在stack中繼續佔用空間。
  • 棧與堆的分離使得不一樣線程能夠訪問同一個對象,這是一種有效的數據交互方式(共享內存);此外也節省了空間,由於堆中的共享常量和緩存能夠被全部棧訪問。

參考

  1. https://simplesnippets.tech/execution-process-of-java-program-in-detail-working-of-just-it-time-compiler-jit-in-detail/
  2. https://blog.csdn.net/yfqnihao/article/details/8289363
  3. https://www.zhihu.com/question/29833675

有問題歡迎你們在評論區留言,轉載請註明出處。

相關文章
相關標籤/搜索