解鎖JVM成神之路(一)
jvm指的是Java虛擬機器,一種能夠執行Java位元組碼的虛擬機器。它能夠模擬具有完整硬體系統功能,執行在一個完全隔離環境中的完整計算機系統。作為一種程式語言的虛擬機器,它不只是專注於Java語言,只要生成編譯檔案匹配jvm對載入編譯檔案格式的要求,任何語言都可以由jvm編譯執行。比如scala,kotlin等。
JVM的基本組成結構
jvm由三部分組成:
類載入子系統
執行時資料區(記憶體空間,記憶體結構)
執行引擎
結構圖如下:
深入瞭解執行時資料區
執行緒共享:
方法區(1。8之後已經不叫方法區了,叫元資料區):用來儲存被jvm載入的類資訊,常量,靜態變數等資料。也就是類的所有屬性,方法以及構造器,介面程式碼都在這裡定義。
這個區域包含一個很重要的區域叫常量池。class物件除了類的版本,屬性,方法等資訊外,還有一些常量以及字面值,而這些幾乎都存在於常量池裡,這部分內容將在被載入的過程中存放到常量池裡。
堆:例項化的物件以及陣列都存在這個區域。這個區域的cg最活躍,也就是cg進行垃圾回收的重要場所。這個區域,cg是以分代回收演算法進行垃圾回收的。從cg的角度看,堆又可以分為:新生代和老年代。
執行緒私有:
程式計數器:也就是一個指標,指向方法區中的方法位元組碼。也就是用於儲存每條執行緒的執行位元組碼指令地址。每條執行緒都會擁有自己的程式計數器,以便執行緒切換的時候,能使程式正常執行。
jvm棧:儲存著每一個棧幀。每個方法的執行都對應著每一個棧幀,棧幀裡放著區域性變量表,運算元棧,動態連線,方法出口等資訊。每個方法的執行以及結束都對應到棧幀的入棧與出棧。
本地方法棧:這個跟jvm棧差不多,只是jvm棧為我們的java服務的,而本地方法棧是為native 方法服務。
深入瞭解類載入子系統
我們的Java檔案被jvm編譯器編譯成class物件之後,在runtime時,由類載入子系統將class位元組碼檔案載入到執行時資料區中。但是,類載入子系統在類的整個生命週期中主要的工作是什麼呢?類的生命週期:
類的生命週期分為:載入——>連線——>初始化——>使用——>解除安裝而連線過程又分為:驗證——>準備——>解析。其實這個過程就是類載入子系統工作的過程。
一,工作過程
1,載入:將class位元組碼檔案載入到執行時資料區中。
2。1,驗證:1,檢查位元組碼檔案是否正確。2,檢查檔案頭的magic number是否正確。
何為magic number?我們用編輯器開啟隨意一個class檔案,發現都是以CAFEBABE開頭。把這資料改掉或者刪除,都會出錯。這個就是magic number
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還有許許多多的知識,比如垃圾回收機制,調優等。
本人水平有限,難免有錯誤或遺漏之處,望大家指正和諒解,提出寶貴意見,願與之交流。