JVM之類加載子系統

      網友投稿 715 2022-05-29

      大家好,我是程序員學長。

      讀前福利,最全pdf獲取

      聯系我

      JVM之類加載子系統

      從今天開始,我們開啟一個新的系列文章–JVM(java虛擬機)系列。

      (本系列文章是基于JDK8(HotSpot Vm)進行討論)

      首先,先給大家安利一個我覺得不錯的 jvm 相關的視頻教程-尚硅谷宋紅康老師java虛擬機

      https://www.bilibili.com/video/BV1PJ411n7xZ?from=search&seid=945512863733786865&spm_id_from=333.337.0.0

      首先,我們先思考一個問題,一個class文件是如何被java虛擬機加載執行的呢?

      帶著這個問題,我們來進入今天要分享的JVM系列之-類加載子系統。

      類加載子系統

      類加載子系統在 java 虛擬機的位置如下圖所示。

      一個 .Class 文件需要被類加載系統加載后,才能成為被虛擬機直接使用的Java類型。

      類加載過程

      類加載的過程主要分為加載、驗證、準備、解析、初始化,其中驗證、準備、解析三個部分統稱為鏈接。

      下面我們來看一下類的加載過程。

      1)通過一個類的全限定名來獲取定義此類的二進制字節流

      2)將這個字節流所代表的靜態存儲結構轉化為方法區的運行時數據結構

      3)在內存中生成一個代表這個類的java.lang.Class對象,作為方法區這個類的各種數據的訪問入口。

      目的在于確保Class文件的字節流中包含的信息符合當前虛擬機的要求,保證被加載類的正確性,不會危害虛擬機自身的安全。主要包括以下4點。

      1)文件格式的驗證

      驗證字節流是否符合Class文件格式的規范,并且能被當前版本的虛擬機處理。

      2)元數據的驗證

      3)字節碼的驗證

      4)符號引用的驗證

      為類變量分配內存并設置該類變量的默認初始值,即零值。

      注意這里不包括用 final 修飾的 static,因為 final 在編譯的時候就會分配了,準備階段會顯示初始化。

      這里不會為實例變量進行內存分配,實例變量將會在對象實例化時隨著對象一起分配在Java堆中。

      考點來了:

      public static int value = 10;

      此時變量 value 在準備階段過后的初始值為 0 而不是 10,因為這時尚未開始執行任何 Java 方法,而把 value 賦值為 10 的 putstatic 指令是程序被編譯后,存放于類構造器 () 方法之中,所以把value賦值為 10 的動作要到類的初始化階段才會被執行。

      public final static int value = 10;

      此時變量value 在準備階段過后的初始值為 10。

      將常量池內的符號引用轉化為直接引用的過程。

      解析動作主要針對類或接口、字段、類方法、接口方法、方法類型、方法句柄和調用點限定符這 7 類符號引用進行,分別對應于常量池的CONSTANT_Class_info、CON-STANT_Fieldref_info、 CONSTANT_Methodref_info、CONSTANT_InterfaceMethodref_info、 CONSTANT_MethodType_info、CONSTANT_MethodHandle_info、CONSTANT_Dyna-mic_info 和 CONSTANT_InvokeDynamic_info 8種常量類型。

      初始化階段就是執行類構造器()方法的過程。

      ()方法不是由程序員在Java代碼中直接編寫的方法,它是Javac編譯器自動收集類中所有類變量的賦值動作和靜態代碼塊中的語句合并而來,編譯器收集的順序是由語句在源文件中出現的順序決定的。

      Java虛擬機會保證在子類的()方法執行前,父類的()方法已經執行完畢。

      Java虛擬機必須保證一個類的()方法在多線程環境中被正確地加鎖同步,如果多個線程同時去初始化一個類,那么只會有其中一個線程去執行這個類的()方法,其他線程都需要阻塞等待,直到活動線程執行完畢()方法。

      public class ClassLoaderDemo { static class DeadLoop{ static { if(true){ System.out.println(Thread.currentThread().getName() + "初始化當前類"); while (true){ } } } } public static void main(String[] args) { Runnable script = new Runnable() { public void run() { System.out.println(Thread.currentThread() + "start"); DeadLoop dlc = new DeadLoop(); } }; Thread thread1 = new Thread(script); Thread thread2 = new Thread(script); thread1.start(); thread2.start(); } }

      結果:

      Thread[Thread-0,5,main]start Thread[Thread-1,5,main]start Thread-0初始化當前類

      類加載器的分類

      站在java虛擬機的角度來看,只存在兩種不同的類加載器:一種是啟動類加載器(Bootstrap ClassLoader),這個類加載器使用C++語言實現,是虛擬機自身的一部分;另外一種就是其他所有的類加載器,這些類加載器都由Java語言實現,獨立存在于虛擬機外部,并且全都繼承自抽象類java.lang.ClassLoader。

      站在Java開發人員的角度來看,類加載器就應當劃分得更細致一些。自JDK 1.2以來,Java一直保持著三層類加載器、雙親委派的類加載架構,盡管這套架構在Java模塊化系統出現后有了一些調整變動,但依然未改變其主體結構,

      整體架構如下圖所示:

      1、啟動類加載器

      它負責加載存放在 \lib目錄,或者被-Xbootclasspath參數所指定的路徑中存放的,而且是Java虛擬機能夠識別的(按照文件名識別,如rt.jar、tools.jar,名字不符合的類庫即使放在lib目錄中也不會被加載)類庫加載到虛擬機的內存中。

      2、擴展類加載器

      這個類加載器是在類 sun.misc.Launcher$ExtClassLoader 中以Java代碼的形式實現的。它負責加載\lib\ext目錄中,或者被java.ext.dirs系統變量所指定的路徑中所有的類庫。

      3、應用程序類加載器

      這個類加載器由sun.misc.Launcher$AppClassLoader來實現。由于應用程序類加載器是ClassLoader類中的getSystemClassLoader()方法的返回值,所以有些場合中也稱它為“系統類加載器”。它負責加載用戶類路徑 (ClassPath)上所有的類庫,開發者同樣可以直接在代碼中使用這個類加載器。如果應用程序中沒有自定義過自己的類加載器,一般情況下這個就是程序中默認的類加載器。

      4、用戶自定義類加載器

      在java的應用程序開發過程中,類的加載幾乎都是由以上三種類加載器相互配合來完成加載的,在必要時,我們也可以自定義的類加載器來進行拓展,典型的如增加除了磁盤位置之外的Class文件來源,或者通過類加載器實現類的隔離、重載等功能。

      下面我們通過一個例子來看一下如何獲取一個類的加載器。

      public class ClassLoaderTest { public static void main(String[] args) { //獲得系統類加載器 ClassLoader systemClassLoader=ClassLoader.getSystemClassLoader(); System.out.println(systemClassLoader); //獲取其上層加載器,擴展類加載器 ClassLoader extClassLoader=systemClassLoader.getParent(); System.out.println(extClassLoader); //獲取其上層加載器,根加載器 ClassLoader bootstrapClassLoader=extClassLoader.getParent(); System.out.println(bootstrapClassLoader); } }

      sun.misc.Launcher$AppClassLoader@18b4aac2 sun.misc.Launcher$ExtClassLoader@610455d6 null

      通過輸出結果可知,我們無法直接通過代碼獲取到根加載器。

      這個點也是面試中經常問到的~

      上圖展示的各種類加載器之間的層次關系被稱為類加載器的 “ 雙親委派模型(Parents Delegation Model)”。雙親委派模型要求除了頂層的啟動類加載器外,其余的類加載器都應有自己的父類加載器。不過這里類加載器之間的父子關系一般不是以繼承(Inheritance)的關系來實現的,而是通常使用組合(Composition)關系來復用父加載器的代碼。

      java虛擬機對class文件采用按需加載的方式,也就是說當需要使用該類時才會將它的class文件加載到內存。在加載class文件時,java虛擬機采用的是雙親委派模式,即把請求交給父類處理,其工作原理如下所示。

      1.如果一個類加載器收到了類加載請求,它并不會自己先去加載,而是把這個請求委托給父類的加載器去執行。

      2.如果父類加載器還存在其父類加載器,則進一步向上委托,依次遞歸,請求最終達到頂層的啟動類加載器。

      3.如果父類加載器可以完成類加載任務,就成功返回,倘若父類加載器無法完成此加載任務,子加載器才會嘗試自己去加載,這就是雙親委派模式。

      自定義String類,但是在加載自定義String類的時候會率先使用引導類加載器加載,而引導類加載器在加載的過程中會先加載jdk自帶的文件(rt.jar包中的java.lang.String.class),報錯信息說沒有main方法,就是因為加載的是rt.jar包中的String類。這樣可以保證對java核心源代碼的保護,這就是沙箱安全機制。

      1、避免類的重復加載。

      2、保護程序的安全,防止核心API被篡改。

      如何判斷兩個class對象是否相同?

      在 jvm 中表示兩個class對象是否是同一個類存在兩個必要條件

      1、類的完整類名必須一致,包括包名。

      2、加載這個類的 ClassLoader (指 ClassLoader 實例對象) 必須相同。

      也就說明,在JVM中,即使這兩個類對象(class對象)來源于同一個Class文件,被同一個虛擬機所加載,但只要加載他們的ClassLoader 實例對象不同,那么這兩個類對象也是不相同的。

      到此為止,我們就把JVM的類加載子系統聊完了,如果覺得不錯,轉發、在看、安排起來吧。

      你知道的越多,你的思維越開闊。我們下期再見。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:linux目錄操作
      下一篇:計算機組成原理-第二章 數據表示與運算
      相關文章
      亚洲精品福利在线观看| 国内成人精品亚洲日本语音| 精品亚洲视频在线| 亚洲欧美日韩综合久久久| 亚洲国产成人无码av在线播放| 亚洲av一综合av一区| 亚洲精品成人无码中文毛片不卡| 久久久精品国产亚洲成人满18免费网站| 无码欧精品亚洲日韩一区夜夜嗨 | 亚洲一区免费观看| 久久99国产亚洲精品观看| 国产AV无码专区亚洲AVJULIA| 国产V亚洲V天堂A无码| 国产成人精品日本亚洲网站| 亚洲第一AAAAA片| 亚洲国产人成在线观看69网站 | 亚洲av无码专区在线播放 | 无码乱人伦一区二区亚洲| 亚洲综合无码一区二区| 在线观看亚洲人成网站| 1区1区3区4区产品亚洲| 亚洲精品在线电影| 亚洲三级视频在线| 亚洲一区二区三区高清不卡| 亚洲中文精品久久久久久不卡| 亚洲成在人线aⅴ免费毛片| 国产精品亚洲综合| 亚洲精品亚洲人成在线观看下载| 国产成人亚洲综合| 国产亚洲av片在线观看播放| 亚洲av日韩av天堂影片精品| 一区二区三区亚洲| 亚洲中文字幕人成乱码| 亚洲午夜无码久久| 日韩亚洲精品福利| 亚洲中文字幕久久精品无码APP | 久久乐国产综合亚洲精品| 亚洲欧洲国产综合AV无码久久| 在线91精品亚洲网站精品成人| 精品亚洲成α人无码成α在线观看 | 亚洲国产日韩在线一区|