什麼是JVM?一文簡談運行機制及基本原理!

什麼是JVM?一文簡談運行機制及基本原理!

JVM的基礎概念

JVM的中文名稱叫Java虛擬機,它是由軟件技術模擬出計算機運行的一個虛擬的計算機。

JVM也充當着一個翻譯官的角色,我們編寫出的Java程序,是不能夠被操作系統所直接識別的,這時候JVM的作用就體現出來了,它負責把我們的程序翻譯給系統“聽”,告訴它我們的程序需要做什麼操作。

我們都知道Java的程序需要經過編譯後,產生.Class文件,JVM才能識別並運行它,JVM針對每個操作系統開發其對應的解釋器,所以只要其操作系統有對應版本的JVM,那麼這份Java編譯後的代碼就能夠運行起來,這就是Java能一次編譯,到處運行的原因。

JVM的生命週期

JVM在Java程序開始執行的時候,它才運行,程序結束的時它就停止。

一個Java程序會開啓一個JVM進程,如果一臺機器上運行三個程序,那麼就會有三個運行中的JVM進程。

JVM中的線程分爲兩種:守護線程和普通線程

守護線程是JVM自己使用的線程,比如垃圾回收(GC)就是一個守護線程。

普通線程一般是Java程序的線程,只要JVM中有普通線程在執行,那麼JVM就不會停止。

權限足夠的話,可以調用exit()方法終止程序。

JVM的結構體系

什麼是JVM?一文簡談運行機制及基本原理!

JVM的啓動過程

1、JVM的裝入環境和配置

在學習這個之前,我們需要了解一件事情,就是JDK和JRE的區別。

JDK是面向開發人員使用的SDK,它提供了Java的開發環境和運行環境,JDK中包含了JRE。

JRE是Java的運行環境,是面向所有Java程序的使用者,包括開發者。

JRE = 運行環境 = JVM

如果安裝了JDK,會發現電腦中有兩套JRE,一套位於/Java/jre.../下,一套位於/Java/jdk.../jre下。那麼問題來了,一臺機器上有兩套以上JRE,誰來決定運行那一套呢?這個任務就落到java.exe身上,java.exe的任務就是找到合適的JRE來運行java程序。

java.exe按照以下的順序來選擇JRE:

  1. 自己目錄下有沒有JRE

  2. 父目錄下有沒有JRE

  3. 查詢註冊表: HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Java Runtime Environment\"當前JRE版本號"\JavaHome

這幾步的主要核心是爲了找到JVM的絕對路徑。

jvm.cfg的路徑爲:JRE路徑\lib\"CPU架構"\jvm.fig

jvm.cfg的內容大致如下:

  • -client KNOWN 
  • -server KNOWN 
  • -hotspot ALIASED_TO -client 
  • -classic WARN 
  • -native ERROR 
  • -green ERROR

KNOWN 表示存在 、IGNORE 表示不存在 、ALIASED_TO 表示給別的JVM去一個別名

WARN 表示不存在時找一個替代 、ERROR 表示不存在拋出異常

2、裝載JVM

通過第一步找到JVM的路徑後,Java.exe通過LoadJavaVM來裝入JVM文件。

LoadLibrary裝載JVM動態連接庫,然後把JVM中的到處函數JNI_CreateJavaVM和JNI_GetDefaultJavaVMIntArgs 掛接到InvocationFunction 變量的CreateJavaVM和GetDafaultJavaVMInitArgs 函數指針變量上。JVM的裝載工作完成。

3、初始化JVM,獲得本地調用接口

調用InvocationFunction -> CreateJavaVM也就是JVM中JNI_CreateJavaVM方法獲得JNIEnv結構的實例。

4、運行Java程序

JVM運行Java程序的方式有兩種:jar包 與 Class

運行jar 的時候,Java.exe調用GetMainClassName函數,該函數先獲得JNIEnv實例然後調用JarFileJNIEnv類中getManifest(),從其返回的Manifest對象中取getAttrebutes("Main-Class")的值,即jar 包中文件:META-INF/MANIFEST.MF指定的Main-Class的主類名作爲運行的主類。之後main函數會調用Java.c中LoadClass方法裝載該主類(使用JNIEnv實例的FindClass)。

運行Class的時候,main函數直接調用Java.c中的LoadClass方法裝載該類。

Class文件

Class文件由Java編譯器生成,我們創建的.Java文件在經過編譯器後,會變成.Class的文件,這樣才能被JVM所識別並運行。

類加載子系統

類加載子系統也可以稱之爲類加載器,JVM默認提供三個類加載器:

1、BootStrap ClassLoader :稱之爲啓動類加載器,是最頂層的類加載器,負責加載JDK中的核心類庫,如 rt.jar、resources.jar、charsets.jar等

2、Extension ClassLoader:稱之爲擴展類加載器,負責加載Java的擴展類庫,默認加載$JAVA_HOME中jre/lib/*.jar 或 -Djava.ext.dirs指定目錄下的jar包。

3、App ClassLoader:稱之爲系統類加載器,負責加載應用程序classpath目錄下所有jar和class文件。

除了Java默認提供的三個ClassLoader(加載器)之外,我們還可以根據自身需要自定義ClassLoader,自定義ClassLoader必須繼承java.lang.ClassLoader 類。除了BootStrap ClassLoader 之外的另外兩個默認加載器都是繼承自java.lang.ClassLoader 。BootStrap ClassLoader 不是一個普通的Java類,它底層由C++編寫,已嵌入到了JVM的內核當中,當JVM啓動後,BootStrap ClassLoader 也隨之啓動,負責加載完核心類庫後,並構造Extension ClassLoader 和App ClassLoader 類加載器。

類加載器子系統不僅僅負責定位並加載類文件,它還嚴格按照以下步驟做了很多事情:

1、加載:尋找並導入Class文件的二進制信息
2、連接:進行驗證、準備和解析   

  • 驗證:確保導入類型的正確性   
  • 準備:爲類型分配內存並初始化爲默認值   
  • 解析:將字符引用解析爲直接引用

3、初始化:調用Java代碼,初始化類變量爲指定初始值

方法區(Method Area)

在JVM中,類型信息類靜態變量都保存在方法區中,類型信息是由類加載器在類加載的過程中從類文件中提取出來的信息。

需要注意的一點是,常量池也存放於方法區中。

程序中所有的線程共享一個方法區,所以訪問方法區的信息必須確保線程是安全的。如果有兩個線程同時去加載一個類,那麼只能有一個線程被允許去加載這個類,另一個必須等待。

在程序運行時,方法區的大小是可以改變的,程序在運行時可以擴展。

方法區也可以被垃圾回收,但條件非常嚴苛,必須在該類沒有任何引用的情況下

類型信息包括什麼?

1、類型的全名(The fully qualified name of the type)

2、類型的父類型全名(除非沒有父類型,或者父類型是java.lang.Object)(The fully qualified name of the typeís direct superclass)

3、該類型是一個類還是接口(class or an interface)(Whether or not the type is a class )

4、類型的修飾符(public,private,protected,static,final,volatile,transient等)(The typeís modifiers)

5、所有父接口全名的列表(An ordered list of the fully qualified names of any direct superinterfaces)

6、類型的字段信息(Field information)

7、類型的方法信息(Method information)

8、所有靜態類變量(非常量)信息(All class (static) variables declared in the type, except constants)

9、一個指向類加載器的引用(A reference to class ClassLoader)

10、一個指向Class類的引用(A reference to class Class)

11、基本類型的常量池(The constant pool for the type)

方法列表(Method Tables)

爲了更高效的訪問所有保存在方法區中的數據,在方法區中,除了保存上邊的這些類型信息之外,還有一個爲了加快存取速度而設計的數據結構:方法列表。每一個被加載的非抽象類,Java虛擬機都會爲他們產生一個方法列表,這個列表中保存了這個類可能調用的所有實例方法的引用,保存那些父類中調用的方法。

Java堆(JVM堆、Heap)

當Java創建一個類的實例對象或者數組時,都在堆中爲新的對象分配內存

虛擬機中只有一個堆,程序中所有的線程都共享它。

堆佔用的內存空間是最多的。

堆的存取類型爲管道類型,先進先出。

在程序運行中,可以動態的分配堆的內存大小。

堆的內存資源回收是交給JVM GC進行管理的,

Java棧(JVM棧、Stack)

在Java棧中只保存基礎數據類型和自定義對象的引用注意只是對象的引用而不是對象本身哦,對象是保存在堆區中的。

拓展知識:像String、Integer、Byte、Short、Long、Character、Boolean這六個屬於包裝類型,它們是存放於堆中的。

棧的存取類型爲類似於水杯,先進後出。

棧內的數據在超出其作用域後,會被自動釋放掉,它不由JVM GC管理

每一個線程都包含一個棧區,每個棧中的數據都是私有的,其他棧不能訪問。

每個線程都會建立一個操作棧,每個棧又包含了若干個棧幀,每個棧幀對應着每個方法的每次調用,每個棧幀包含了三部分:

局部變量區(方法內基本類型變量、變量對象指針)

操作數棧區(存放方法執行過程中產生的中間結果)

運行環境區(動態連接、正確的方法返回相關信息、異常捕捉)

什麼是JVM?一文簡談運行機制及基本原理!

寫在最後:

歡迎大家關注我新開通的公衆號【風平浪靜如碼】,海量Java相關文章,學習資料都會在裏面更新,整理的資料也會放在裏面。

覺得寫的還不錯的就點個贊,加個關注唄!點關注,不迷路,持續更新!!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章