阿里高頻面試題,熱部署瞭解嗎?

「來源: |稀飯下雪 ID:xifanxiaxue」

不花時間的導讀:這是《好好面試系列》第25篇原創文,該系列主要分享小飯飯面試別人、和被別人面試的經歷,該篇文章主要分享熱部署相關,有興趣的看看,已經知道的可以無視。

熟悉雙親委派模型嗎?

熟悉,這題我會。

機會來了,我八股文大神可是專門修煉過這個的。

我們為啥不能在Java工程中定義同名的String的Java檔案?

(⊙o⊙)…

這。。。。。

多執行緒載入類的時候為什麼沒有執行緒問題?

臥槽 。。。。

首先,在講解這道題之前,先來劃重點

類載入時機與過程

類從被載入到虛擬機器記憶體中開始,到卸載出記憶體為止,它的整個生命週期包括:載入(Loading)、驗證(Verification)、準備(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和解除安裝(Unloading)7個階段。其中準備、驗證、解析3個部分統稱為連線(Linking)

阿里高頻面試題,熱部署瞭解嗎?

載入、驗證、準備、初始化和解除安裝這5個階段的順序是確定的,類的載入過程必須按照這種順序按部就班地開始,而解析階段則不一定:它在某些情況下可以在初始化階段之後再開始,這是為了支援Java語言的執行時繫結(也稱為動態繫結或晚期繫結)

載入

在載入階段(可以參考java。lang。ClassLoader的loadClass()方法),虛擬機器需要完成以下3件事情:

透過一個類的全限定名來獲取定義此類的二進位制位元組流(並沒有指明要從一個Class檔案中獲取,可以從其他渠道,譬如:網路、動態生成、資料庫等);

將這個位元組流所代表的靜態儲存結構轉化為方法區的執行時資料結構;

在記憶體中生成一個代表這個類的java。lang。Class物件,作為方法區這個類的各種資料的訪問入口;

驗證

驗證是連線階段的第一步,這一階段的目的是為了確保Class檔案的位元組流中包含的資訊符合當前虛擬機器的要求,並且不會危害虛擬機器自身的安全。驗證階段大致會完成4個階段的檢驗動作:

檔案格式驗證;

元資料驗證;

位元組碼驗證;

符號引用驗證;

準備

準備階段是正式為類變數分配記憶體並設定類變數初始值的階段,這些變數所使用的記憶體都將在方法區中進行分配。這時候進行記憶體分配的僅包括類變數(被static修飾的變數),而不包括例項變數,例項變數將會在物件例項化時隨著物件一起分配在堆中。其次,這裡所說的初始值

「通常情況」

下是資料型別的零值,

例如:public static int a = 1

這個時候變數a在準備階段過後的初始值為0,而不是1,因為這個時候尚未開始執行任何java方法,而把a賦值為1的指令是程式被編譯後,存放於類構造器()方法中,所以把a賦值為1的動作將在初始化階段才執行。

而當:public static final int a = 2

這種情況又不同了,當類欄位的屬性是ConstantValue時,會在準備階段初始化為指定的值,所以標誌位final之後,a的值再準備階段便被初始化為2,而不再是1 了。

劃重點:這裡其實也好理解,final標誌的常量意味著是不變的,那麼在準備階段直接賦值了就可以了,沒必要多此一舉,先賦初始值,再在初始化的時候額外再做一次。

解析

解析階段是虛擬機器將常量池內的符號引用替換為直接引用的過程。解析動作主要針對類或介面、欄位、類方法、介面方法、方法型別、方法控制代碼和呼叫點限定符7類符號引用進行。

初始化

在介紹初始化時,要先介紹兩個方法:

在編譯生成class檔案時,會自動產生兩個方法,一個是類的初始化方法, 另一個是例項的初始化方法

:在jvm第一次載入class檔案時呼叫,包括靜態變數初始化語句和靜態塊的執行

: 在例項創建出來的時候呼叫,包括呼叫new運算子;呼叫 Class 或 Java。lang。reflect。Constructor 物件的newInstance()方法;呼叫任何現有物件的clone()方法;透過 java。io。ObjectInputStream 類的getObject() 方法反序列化。

類初始化階段是類載入過程的最後一步,到了初始化階段,才真正開始執行類中定義的java程式程式碼。在準備階段、變數已經被賦值過一次系統要求的初始值,而在初始化階段,則根據開發人員透過程式去初始化類變數和其他資源,或者說:初始化階段是執行類構造器()方法的過程。

()方法是由編譯器自動收集類中的所有類變數的賦值動作和靜態語句塊 static{} 中的語句合併產生的,編譯器收集的順序是由語句在原始檔中出現的順序所決定的,靜態語句塊只能訪問到定義在靜態語句塊之前的變數,定義在它之後的變數,在前面的靜態語句塊可以賦值,但是不能訪問。

「總結一大波美女:其實也這個過程不用記,靠理解最好了,你想想看哦,你要把一個類載入到虛擬機器,是不是要有一個載入的過程,這個過程就是將你的class對應的二進位制位元組流載入到虛擬機器中,載入完虛擬機器是不是要校驗下,規則的你給的東西有沒有毒?驗證完了,是不是要幫我們把一些類變數設定下初始值?設定完了還有一些物件標誌啊什麼的,是不是也要幫忙解析成直接引用,最後是不是還要走下我們類的初始化方法。」

雙親委派模型

阿里高頻面試題,熱部署瞭解嗎?

所謂雙親委派是指每次收到類載入請求時,先將請求委派給父類載入器完成(所有載入請求最終會委派到頂層的Bootstrap ClassLoader載入器中),如果父類載入器無法完成這個載入(該載入器的

「搜尋範圍」

中沒有找到對應的類),子類嘗試自己載入, 如果都沒載入到,則會丟擲 ClassNotFoundException 異常, 看到這裡其實就解釋了文章開頭提出的第一個問題,父載入器已經載入了JDK 中的 String。class 檔案,所以我們不能定義同名的 String java 檔案。

這麼做的好處是什麼?

因為這樣可以避免重複載入,當父親已經載入了該類的時候,就沒有必要 ClassLoader 再載入一次。考慮到安全因素,我們試想一下,如果不使用這種委託模式,那我們就可以隨時使用自定義的String來動態替代java核心api中定義的型別,這樣會存在非常大的安全隱患,而雙親委託的方式,就可以避免這種情況,因為String 已經在啟動時就被引導類載入器(Bootstrcp ClassLoader)載入,所以使用者自定義的ClassLoader永遠也無法載入一個自己寫的String,除非你改變 JDK 中 ClassLoader 搜尋類的預設演算法。

我們發現除了啟動類載入器(BootStrap ClassLoader),每個類都有其“父類”載入器,這種組合關係是透過組合模式來決定的,而不是繼承關係。

三種類載入器分別職責是啥?

「Bootstrap ClassLoader」

:這個載入器不是一個Java類,而是由底層的c++實現,負責在虛擬機器啟動時載入Jdk核心類庫(如:rt。jar、resources。jar、charsets。jar等)以及載入後兩個類載入器。這個ClassLoader完全是JVM自己控制的,需要載入哪個類,怎麼載入都是由JVM自己控制,別人也訪問不到這個類

「Extension ClassLoader」

:是一個普通的Java類,繼承自ClassLoader類,負責載入{JAVA_HOME}/jre/lib/ext/目錄下的所有jar包。

「App ClassLoader」

:是Extension ClassLoader的子物件,負責載入應用程式classpath目錄下的所有jar和class檔案。

通常用這兩種方式來動態載入一個 java 類,

「Class.forName()」

「ClassLoader.loadClass()」

,但是兩個方法之間也是有一些細微的差別,具體看上一篇文章:Object詳談

雙親委派模型原始碼解析

來看看核心loadClass的方法

阿里高頻面試題,熱部署瞭解嗎?

image-20210804075639756

可以看到方法有同步塊(synchronized), 這樣就算多執行緒情況也不會出現重複載入的情況。

JAVA熱部署實現

什麼是熱部署(hotswap)?

Java 類是透過 Java 虛擬機器載入的,某個類的 class 檔案在被 classloader 載入後,會生成對應的 Class 物件,之後就可以建立該類的例項,而熱部署是在不重啟 Java 虛擬機器的這一前提下,自動偵測到 class 檔案的變化,更新執行時 class 的行為。

而實際上,對於同一個全限定名的java類,只能被載入一次,而且無法被解除安裝。

那麼竟然類無法被解除安裝,那麼能不能把她媽給換了?也就是類載入器,答案是可以的,我們可以自定義類的載入器,並重寫ClassLoader的findClass方法,然後熱部署步驟就是:

銷燬自定義ClassLoader(被該載入器載入的class也會自動解除安裝);

更新class

使用新的ClassLoader去載入class

最終落地到實現,其實就是將ClassLoader給覆蓋掉,怎麼覆蓋,重新new一個同樣的ClassLoader就可以了。

「總結一大波:我們雖然不能對被載入的class動手動腳,但是卻可以對ClassLoader動手腳,搞掉它,然後使用新的ClassLoader載入class就可以了。」

那麼什麼時候回收掉老的類呢?畢竟替換了,就存在新舊問題了,也就存在記憶體洩露問題了。

通常情況下,在JSP,OSGI及其他一些支援熱替換的庫,都是需要進行類的解除安裝回收的,否則類在替換後,老的類就沒用了但是還在記憶體中,就會造成記憶體洩漏。

類的解除安裝需要滿足以下三個條件:

該類所有的例項都已經被GC,也就是JVM中不存在該Class的任何例項。

載入該類的ClassLoader已經被GC。

該類的java。lang。Class 物件沒有在任何地方被引用,如不能在任何地方透過反射訪問該類的方法。

所以在自定義類載入器時,就要注意這一點,如果你是希望其使用完成後就被解除安裝,那麼就需要特別留意類載入器及類的作用域了。

——《好好面試系列文》——

阿里高頻面試題,熱部署瞭解嗎?

高階開發竟然被構造器迴圈依賴難住了?

阿里高頻面試題,熱部署瞭解嗎?

肥肥的主管和帥氣的小飯飯討論了下ForkJoinPool

阿里高頻面試題,熱部署瞭解嗎?

聊聊Autowired的常考面試題

阿里高頻面試題,熱部署瞭解嗎?

面試官告訴你什麼是JMM和常考面試題

阿里高頻面試題,熱部署瞭解嗎?

去年面了多個候選人,看看我挖的坑還有他們應該要補的Java基礎(二)

阿里高頻面試題,熱部署瞭解嗎?

去年面了多個候選人,看看我挖的坑還有他們應該要補的Java基礎(一)

小飯飯:某遊戲大廠高階開發,專門和主管抬槓的小組長。 想學dubbo的可以微信搜一搜:稀飯下雪,第一時間閱讀Caffeine、dubbo等優秀框架的原始碼分析和教材講解以及實際應用,有需要java面試資料的也可以關注一波,回覆java資源獲取我整理的專項資料。

點個在我,支援一下,愛你

頂部