JVM是怎麼工做的?

瞭解JVM的工做機制可以更好的幫助咱們理解java語言自己,規避各類可能的錯誤。因此,今天趁此機會好好複習一下。來看看JVM是怎麼工做的。java

1、啥是JVMsql

JVM能夠理解爲用來運行java程序的一種運行時引擎(run-time engine)。沒錯,main方法就是由jvm調用做爲入口方法的。JVM是JRE的重要組成部分。bootstrap

JVM爲java程序提供了一次編寫,處處運行的基礎。api

當咱們編譯一個java文件時,就由編譯器生成一個對應的class文件,當咱們運行class文件時,JVM會對class文件進行一系列的調度工做。這就是JVM的職責所在。因此,想要弄清楚JVM的工做原理,就要弄清java程序在運行過程當中每一步都發生了啥子。下圖是JVM的一張架構圖,預覽一下~架構

 

2、JVM的職責oracle

  JVM的職責主要包括三部分app

  裝載(Loading)jvm

  連接(Linking)ui

  初始化(Intialization)spa

2.1 裝載

  類裝載器(Class Loader)讀取class文件,生成對應的二進制數據並將其存儲在方法區中(method Area)。

  對於每一個class文件,JVM都會存儲以下信息:

  ① 裝載類的徹底限定名(Fully qualified name)和他們的直接父類

  ② class文件是否關聯於另外一個類或者接口或者枚舉

  ③ 修飾符,變量和方法信息等等

  裝載class文件後,JVM會在堆區中建立一個Class類型來表明這個文件的類信息。是的,這個Class類型就是咱們日常說的在java.lang包中預約義的那個Class。具體怎麼用這個Class類型,請參考相關文檔。注意,對於每個class文件,只會有一個Class對象被建立,是一對一的關係。

2.2 連接

  連接階段主要是進行驗證(Verification),準備(preparation),和解析(resolution)

  ① 驗證:它主要是確保每個class文件的正確性,如class文件的格式是否符合標準?是否由有效的編譯器生成?若是驗證失敗,咱們將會收到java.lang.VerifyError的運行時異常

  ② 準備:JVM爲類變量分配存儲空間,而且將他們初始化爲默認值。

  ③ 解析:主要是用來將符號引用替換爲直接引用。經過搜索方法區來找到對應定位須要引用的實體。

2.3 初始化

  在這個階段,全部的靜態變量都會初始化爲咱們在代碼中定義的值。執行的順序是:在單個類中,從上到下,在類層次中,由父類到子類。

  接下來就輪到具體的類裝載器了。通常而言,分爲三種類裝載器

  ① 根類裝載器 (Bootstrap class loader):每個JVM都必須有一個根類裝載器,用來裝載可信任的類,例如它會裝載位於JAVA_HOME/JRE/lib中的java api類。這個路徑也常被稱做根目錄(bootstrap path)。注意,根類裝載器不是由JAVA語言編寫,而是由本地語言(Native languages)例如C和C++編寫而成。

  ② 擴展裝載器(Extension class loader):它是根裝載器的子裝載器。主要用於裝載位於JAVA_HOME/jre/lib/ext(也叫做擴展路徑:Extension path)的各類類,或者也能夠由系統屬性java.ext.dirs來指定加載路徑。這個裝載器是由JAVA語言編寫,實現類爲sun.misc.Launcher$ExtClassLoader。

  ③ 系統/應用類裝載器(System/Application class loader): 它是擴展裝載器的子裝載器。主要負責加載位於應用類路徑(application class path)下的類文件。這個是由咱們本身指定。默認狀況下,會使用環境變量的值,該環境變量記錄了java.class.path的值。該裝載器也是由JAVA語言編寫而成,實現類爲sun.misc.Launcher$ExtClassLoader。

  須要特別說明的是,JVM採起了一種委託-分層(Delegation-Hierarchy)機制來加載類文件。系統類裝載器將裝載請求委託給擴展類裝載器,而後再交給根類裝載器。若是在根類路徑下面發現了須要的類文件,那麼就直接加載,不然,會將加載請求回傳給擴展類裝載進行查找,若是還找不到就回傳給系統類裝載器。若是連繫統類裝載器都沒法加載,那麼咱們就會獲得一個運行時錯誤:java.lang.ClassNotFoundException。

3、JVM的內存分佈

  JVM內存的分佈狀況也是咱們須要重點關注的。主要分爲以下幾類:

  ① 方法區(Method Area):在方法區中,包括全部類層次的信息如類名,直接父類名,類方法,成員變量,靜態變量等等。每個JVM只能擁有一個方法區,且方法區是一個共享資源,可供其餘資源訪問調用。

  ② 堆區(Heap Area):全部的對象都存儲在堆區中。每個JVM也只能有一個堆區。它也具備共享性。

  ③ 棧區 (Stack Area):對於每個線程,JVM都會在此處建立一個運行時棧。每個棧塊又叫作活動記錄/棧幀(activation record/stack frame),它們都記錄了方法調用的狀況,全部屬於該方法的局部變量都存於對應的棧幀中。線程結束後,棧幀會被銷燬。棧區不是共享資源,它由對應的線程獨享。

  ④ PC寄存器(PC registors): 用於存儲線程的當前的執行指令的地址。很明顯,每一個線程都有屬於本身的PC寄存器。

  ⑤ 本地方法棧(Native method stacks):對於每一個線程,都會分別建立一個本地方法棧。主要用於存儲本地方法的調用狀況。

 

4、執行引擎(Execution Engine)

  執行引擎用來執行class文件(字節碼)。執行器一行一行的讀取字節碼,結合當前內存區存儲的各類數據和信息,執行相關指令。執行器也能夠分爲三個部分

  ① 解釋器(Interpreter):用來逐行解釋字節碼並執行。缺點就是若是同一個方法調用屢次,每一次都仍是須要進行解釋,效率低下浪費資源。

  ② 實時編譯器 (JIT- Just In Time Compiler):主要用來提高解釋器的執行效率。它會將整個的字節碼進行編譯並將其轉化爲本地碼(Native Code)。如此一來,每當解釋器看到對應的方法調用時,均可以直接使用JIT提供的本地碼而不須要再次進行解釋。從而也提高了效率。

  ③ 垃圾回收器(Garbage Collector):主要用於銷燬沒有引用的對象。

5、常見問題

5.1 上文中出現的「本地方法」究竟是個什麼東西?

  這其實和一個關鍵的概念有關:JNI。JNI也叫java本地接口,該接口可以與本地方法(Native Method Libraries)庫進行交互,爲程序的執行提供本地庫(如C/C++)的支持。它可以讓JVM調用C/C++庫或者被C/C++庫調用。後者主要是在硬件特性比較特殊的狀況下使用。 

     本地方法庫主要是指本地庫(C/C++)的集合,執行器會用到這些庫。

  native這個詞其實也是JAVA語言的關鍵詞之一。通常狀況下用的比較少。可是它有以下幾個做用:

  It allows you to:

    •   call a compiled dynamically loaded library (here written in C) with arbitrary assembly code from Java
    •   and get results back into Java

  This could be used to:

    •   write faster code on a critical section with better CPU assembly instructions (not CPU portable)
    •   make direct system calls (not OS portable)

  with the tradeoff of lower portability.

5.2 靜態變量會被垃圾回收器回收嗎?

  當類被加載後,靜態變量屬於上文的方法區,很明顯,他不會被回收。可是,除非用來加載這個類的類加載器被回收了,那這個靜態方法也就會被回收。如此,通常狀況下就能夠理解爲never。也請注意,根類裝載器是不會被回收的。

5.3 Class.forName都幹了些啥?

  它啊,就是用於加載類啊,而且初始化靜態變量。

  主要是有不少人問到在老版本的jdbc爲啥須要Class.forName("oracle.jdbc.driver.OracleDriver")這個東東。

  注意啊,這個是老版本的需求,如今已經不用了:

In previous versions of JDBC, to obtain a connection, you first had to initialize your JDBC driver by calling the method Class.forName. This methods required an object of   type java.sql.Driver. Each JDBC driver contains one or more classes that implements the interface java.sql.Driver.
...
Any JDBC 4.0 drivers that are found in your class path are automatically loaded. (However, you must manually load any drivers prior to JDBC 4.0 with the method Class.forName.)

  說白了用Class.forName就是保證在不用顯式import的狀況下來使用對應的類,也便是說,能夠在classpath下不存在相關類文件時,仍舊能夠構建項目,加載指定類文件。

相關文章
相關標籤/搜索