友快網

導航選單

大廠碼農漲薪10k的秘訣:JVM的類載入機制你是否理解到這個程度?

今日分享開始啦,請大家多多指教~

1。 類載入階段

1。1 載入階段

將類的位元組碼載入方法區中,內部採用 C++ 的 instanceKlass 描述 java 類,它的重要 field 有:

_java_mirror 即 java 的類映象,例如對 String 來說,就是 String。class,作用是把 klass 暴 露給 java 使用

_super 即父類

_fields 即成員變數

_methods 即方法

_constants 即常量池

_class_loader 即類載入器

_vtable 虛方法表

_itable 介面方法表

如果這個類還有父類沒有載入,則先觸發父類的載入。

載入和連結可能是交替執行的。

注意:

instanceKlass 這樣的【元資料】是儲存在方法區(1。8 後的元空間內),但 _java_mirror 是儲存在堆中

可以透過前面介紹的 HSDB 工具檢視

1。2 連結階段

驗證

驗證類是否符合 JVM規範,安全性檢查,阻止不合法的類繼續執行。用 UE 等支援二進位制的編輯器修改 HelloWorld。class的魔數,在控制檯執行:

準備

為 static 變數分配空間,設定預設值:

static 變數在 JDK 7 之前儲存於 instanceKlass 末尾,從 JDK 7 開始,儲存於 _java_mirror 末尾

static 變數分配空間和賦值是兩個步驟,分配空間在準備階段完成,賦值在初始化階段完成

如果 static 變數是 final 的基本型別,以及字串常量,那麼編譯階段值就確定了,賦值在準備階段完成

如果 static 變數是 final 的,但屬於引用型別,那麼賦值也會在初始化階段完成

將常量池中的符號引用解析為直接引用

解析

將常量池中的符號引用解析為直接引用:

1。3 初始化階段

< init()> V 方法

初始化即呼叫 < cinit>()V ,虛擬機器會保證這個類的『構造方法』的執行緒安全。

發生的時機

概括地說,類初始化是【懶惰的】

main 方法所在的類,總會被首先初始化

首次訪問這個類的靜態變數或靜態方法時

子類初始化,如果父類還沒初始化,會引發

子類訪問父類的靜態變數,只會觸發父類的初始化

Class。forName

new 會導致初始化

不會導致類初始化的情況:

訪問類的 static final 靜態常量(基本型別和字串)不會觸發初始化

類物件。class 不會觸發初始化

建立該類的陣列不會觸發初始化

類載入器的 loadClass 方法

測試程式碼:

驗證(測試時請先全部註釋,每次只執行其中一個)

1。4 練習

從位元組碼分析,使用 a,b,c 這三個常量是否會導致 E 初始化:

典型應用 - 完成懶惰初始化單例模式:

以上的實現特點是:

懶惰例項化

初始化時的執行緒安全是有保障的

2。 類載入器

以 JDK 8 為例:

類載入器的優先順序(由高到低):

啟動類載入器 -> 擴充套件類載入器 -> 應用程式類載入器 -> 自定義類載入器

2。1 啟動類載入器

用 Bootstrap 類載入器載入類:

執行:

輸出:

-Xbootclasspath 表示設定 bootclasspath

其中 /a:。 表示將當前目錄追加至 bootclasspath 之後

可以有以下幾個方式替換啟動類路徑下的核心類:

java -Xbootclasspath: < new bootclasspath>

前追加:java -Xbootclasspath/a:<追加路徑>

後追加:java -Xbootclasspath/p:<追加路徑>

2。2 擴充套件類載入器

程式執行:

輸出結果:

寫一個同名的類:

打個 jar 包:

將 jar 包複製到JAVA_HOME/jre/lib/ext(擴充套件類載入器載入的類必須是以jar包方式存在),重新執行 Load5_2

輸出:

2。3 雙親委派模式

所謂的雙親委派,就是指呼叫類載入器的 loadClass 方法時,查詢類的規則。

注意:這裡的雙親,翻譯為上級似乎更為合適,因為它們並沒有繼承關係

大廠碼農漲薪10k的秘訣:JVM的類載入機制你是否理解到這個程度?

大廠碼農漲薪10k的秘訣:JVM的類載入機制你是否理解到這個程度?

例如:

大廠碼農漲薪10k的秘訣:JVM的類載入機制你是否理解到這個程度?

執行流程為:

sun。misc。Launcher$AppClassLoader // 1 處, 開始檢視已載入的類,結果沒有

sun。misc。Launcher$AppClassLoader // 2 處,委派上級 sun。misc。Launcher$ExtClassLoader。loadClass()

sun。misc。Launcher$ExtClassLoader // 1 處,檢視已載入的類,結果沒有

sun。misc。Launcher$ExtClassLoader // 3 處,沒有上級了,則委派 BootstrapClassLoader 查詢

BootstrapClassLoader 是在 JAVA_HOME/jre/lib 下找 H 這個類,顯然沒有

sun。misc。Launcher$ExtClassLoader // 4 處,呼叫自己的 findClass 方法,是在JAVA_HOME/jre/lib/ext 下找 H 這個類,顯然沒有,回到 sun。misc。Launcher$AppClassLoader 的 // 2 處

繼續執行到 sun。misc。Launcher$AppClassLoader // 4 處,呼叫它自己的 findClass 方法,在 classpath 下查詢,找到了

2。4 執行緒上下文類載入器

我們在使用 JDBC 時,都需要載入 Driver 驅動,不知道你注意到沒有,不寫

Class。forName(“com。mysql。jdbc。Driver”)

也是可以讓 com。mysql。jdbc。Driver 正確載入的,你知道是怎麼做的嗎? 讓我們追蹤一下原始碼:

大廠碼農漲薪10k的秘訣:JVM的類載入機制你是否理解到這個程度?

先不看別的,看看 DriverManager 的類載入器:

System。out。println(DriverManager。class。getClassLoader());

列印 null,表示它的類載入器是 Bootstrap ClassLoader,回到 JAVA_HOME/jre/lib 下搜尋類,但 JAVA_HOME/jre/lib 下顯然沒有 mysql-connector-java-5。1。47。jar 包,這樣問題來了,在 DriverManager 的靜態程式碼塊中,怎麼能正確載入 com。mysql。jdbc。Driver 呢?

繼續看 loadInitialDrivers() 方法:

大廠碼農漲薪10k的秘訣:JVM的類載入機制你是否理解到這個程度?

大廠碼農漲薪10k的秘訣:JVM的類載入機制你是否理解到這個程度?

先看 2)發現它最後是使用 Class。forName 完成類的載入和初始化,關聯的是應用程式類載入器,因此 可以順利完成類載入

再看 1)它就是大名鼎鼎的 Service Provider Interface (SPI)

約定如下,在 jar 包的 META-INF/services 包下,以介面全限定命名為檔案,檔案內容是實現類名稱

大廠碼農漲薪10k的秘訣:JVM的類載入機制你是否理解到這個程度?

這樣就可以使用:

大廠碼農漲薪10k的秘訣:JVM的類載入機制你是否理解到這個程度?

來得到實現類,體現的是【面向介面程式設計+解耦】的思想,在下面一些框架中都運用了此思想:

JDBC

Servlet 初始化器

Spring 容器

Dubbo(對 SPI 進行了擴充套件)

接著看 ServiceLoader。load 方法:

大廠碼農漲薪10k的秘訣:JVM的類載入機制你是否理解到這個程度?

執行緒上下文類載入器是當前執行緒使用的類載入器,預設就是應用程式類載入器,它內部又是由 Class。forName 呼叫了執行緒上下文類載入器完成類載入,具體程式碼在 ServiceLoader 的內部類 LazyIterator 中:

大廠碼農漲薪10k的秘訣:JVM的類載入機制你是否理解到這個程度?

2。5 自定義類載入器

問問自己,什麼時候需要自定義類載入器:

1)想載入非 classpath 隨意路徑中的類檔案

2)都是透過介面來使用實現,希望解耦時,常用在框架設計

3)這些類希望予以隔離,不同應用的同名類都可以載入,不衝突,常見於 tomcat 容器

步驟:

繼承 ClassLoader 父類

要遵從雙親委派機制,重寫 findClass 方法 注意不是重寫 loadClass 方法,否則不會走雙親委派機制

讀取類檔案的位元組碼

呼叫父類的 defineClass 方法來載入類

使用者呼叫該類載入器的 loadClass 方法

今日份分享已結束,請大家多多包涵和指點!

上一篇:小米遭遇強敵, 65W閃充+6400萬三攝, 12GB運存曲面屏旗艦僅2399元
下一篇:無敵!阿里p8大佬強推的這款IDEA高效外掛,檢測程式碼漏洞一鍵修復