首頁歷史 > 正文

解鎖JVM成神之路(一)

2022-02-23由 我在打工還債 發表于 歷史

jvm指的是Java虛擬機器,一種能夠執行Java位元組碼的虛擬機器。它能夠模擬具有完整硬體系統功能,執行在一個完全隔離環境中的完整計算機系統。作為一種程式語言的虛擬機器,它不只是專注於Java語言,只要生成編譯檔案匹配jvm對載入編譯檔案格式的要求,任何語言都可以由jvm編譯執行。比如scala,kotlin等。

JVM的基本組成結構

jvm由三部分組成:

類載入子系統

執行時資料區(記憶體空間,記憶體結構)

執行引擎

結構圖如下:

解鎖JVM成神之路(一)

深入瞭解執行時資料區

解鎖JVM成神之路(一)

執行緒共享:

方法區(1。8之後已經不叫方法區了,叫元資料區):用來儲存被jvm載入的類資訊,常量,靜態變數等資料。也就是類的所有屬性,方法以及構造器,介面程式碼都在這裡定義。

這個區域包含一個很重要的區域叫常量池。class物件除了類的版本,屬性,方法等資訊外,還有一些常量以及字面值,而這些幾乎都存在於常量池裡,這部分內容將在被載入的過程中存放到常量池裡。

堆:例項化的物件以及陣列都存在這個區域。這個區域的cg最活躍,也就是cg進行垃圾回收的重要場所。這個區域,cg是以分代回收演算法進行垃圾回收的。從cg的角度看,堆又可以分為:新生代和老年代。

執行緒私有:

程式計數器:也就是一個指標,指向方法區中的方法位元組碼。也就是用於儲存每條執行緒的執行位元組碼指令地址。每條執行緒都會擁有自己的程式計數器,以便執行緒切換的時候,能使程式正常執行。

jvm棧:儲存著每一個棧幀。每個方法的執行都對應著每一個棧幀,棧幀裡放著區域性變量表,運算元棧,動態連線,方法出口等資訊。每個方法的執行以及結束都對應到棧幀的入棧與出棧。

解鎖JVM成神之路(一)

本地方法棧:這個跟jvm棧差不多,只是jvm棧為我們的java服務的,而本地方法棧是為native 方法服務。

深入瞭解類載入子系統

我們的Java檔案被jvm編譯器編譯成class物件之後,在runtime時,由類載入子系統將class位元組碼檔案載入到執行時資料區中。但是,類載入子系統在類的整個生命週期中主要的工作是什麼呢?類的生命週期:

解鎖JVM成神之路(一)

類的生命週期分為:載入——>連線——>初始化——>使用——>解除安裝而連線過程又分為:驗證——>準備——>解析。其實這個過程就是類載入子系統工作的過程。

一,工作過程

1,載入:將class位元組碼檔案載入到執行時資料區中。

2。1,驗證:1,檢查位元組碼檔案是否正確。2,檢查檔案頭的magic number是否正確。

何為magic number?我們用編輯器開啟隨意一個class檔案,發現都是以CAFEBABE開頭。把這資料改掉或者刪除,都會出錯。這個就是magic number

解鎖JVM成神之路(一)

2。2,準備:給類的靜態變數分配記憶體,賦予初始值。這裡的初始值並不是賦值的值。打個比方:private int i = 100; 在準備階段,並不是將100賦值給i。而是給i賦值0,因為int的預設初始值是0。

2。3,解析:檢查指定的類是否引用了其他的類或者介面,是否能正常引入。也就是裝載當前類引入依賴的其他類。

3,初始化:這裡才是給類的靜態變數賦正確的初始值。i的值由0變為100。

使用,解除安裝這就沒什麼可說的了。

透過原始碼瞭解載入過程:在rt。jar包中的java。lang。ClassLoader類中,我們可以檢視類載入實現過程的程式碼,具體原始碼如下:

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{ synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System。nanoTime(); try { if (parent != null) { c = parent。loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // If still not found, then invoke findClass in order // to find the class。 long t1 = System。nanoTime(); c = findClass(name); // this is the defining class loader; record the stats sun。misc。PerfCounter。getParentDelegationTime()。addTime(t1 - t0); sun。misc。PerfCounter。getFindClassTime()。addElapsedTimeFrom(t1); sun。misc。PerfCounter。getFindClasses()。increment(); } } if (resolve) { resolveClass(c); } return c; }}

原始碼的註釋寫得很清楚了:

載入的時候使用了synchronized加鎖機制,也就是當前執行緒有在載入當前這個類時,不允許其他類進行載入。

判斷這個類是否已經被載入,如果已經載入了,就不再載入。如果沒有,則要載入。

我們在看原始碼時,看到

//從parent這個成員變數中,我們得知 private final ClassLoader parent; try { if (parent != null) { c = parent。loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } }

這是一個很重要的資訊~由此引申一個下面很重要的概念

雙親委派機制

我們先了解類載入器的種類:

啟動類載入器Bootstrap ClassLoader:負責將java_home/lib下的類載入到記憶體中

擴充套件類載入器Extension ClassLoader:負責將java_home/lib/extde的類載入到記憶體中

系統類載入器Application ClassLoader:負責將classpath的類載入到記憶體中

使用者自定義載入器User ClassLoader:負責載入使用者自定義路徑下的類包

在看原始碼可以看出,每次載入類的時候,當前載入器都將該類一層一層交給parent,而且每次都檢查是否已經載入了,如果載入了就不再載入。當交給parent時,如果parent能載入,則就載入。如果parent都不能載入,則由當前載入器進行載入。

雙親委派的優勢:

沙箱安全機制:比如自己寫的String。class類不會被載入,這樣可以防止核心庫被隨意篡改

避免類的重複載入:當父ClassLoader已經載入了該類的時候,就不需要子CJlassLoader再載入一次

以上就是jvm的記憶體結構以及類載入子系統的相關內容。jvm還有許許多多的知識,比如垃圾回收機制,調優等。

本人水平有限,難免有錯誤或遺漏之處,望大家指正和諒解,提出寶貴意見,願與之交流。

頂部