《面試1v1》JVM類加載過(guò)程
我是 javapub,一名 Markdown 程序員從?????,八股文種子選手。
面試官: 你了解Java的類加載過(guò)程嗎?跟我聊聊classes是如何加載到JVM中的。
候選人: Java的類加載過(guò)程由加載、驗(yàn)證、準(zhǔn)備、解析和初始化5個(gè)階段組成。當(dāng)我們使用java命令執(zhí)行一個(gè)類時(shí),JVM會(huì)首先搜索類的加載路徑,這包括Bootstrap Classpath、Extension Classpath和Application Classpath。
面試官: 哈哈,這也太官方了吧,來(lái),我們以更口語(yǔ)的方式探討下類加載過(guò)程。想象你是一名新手Java程序員,剛?cè)肼氁粋€(gè)公司,被分配一個(gè)任務(wù)需要執(zhí)行一個(gè)Java類,你會(huì)有什么疑惑或者過(guò)程?
候選人: 好的,那我來(lái)思考下當(dāng)初我第一次運(yùn)行Java程序的時(shí)候的內(nèi)心活動(dòng): 天啊,我首先得搞清楚要運(yùn)行的這個(gè)類到底在哪兒?難道要我一個(gè)個(gè)文件翻找嗎?那還不如讓我直接讀JVM的源碼來(lái)找呢! Wait,原來(lái)JVM已經(jīng)把這事兒都幫我干了,它會(huì)去找 classpath 下的文件,包括環(huán)境變量里設(shè)置的那堆classpath。這肯定是個(gè)苦力活,幸好有JVM這個(gè)工具哥幫忙! 找到類文件了,接下來(lái)JVM該干嘛?嗯,它得確定這個(gè)類里寫的是否都是正確的Java語(yǔ)法,不會(huì)誤導(dǎo)小菜鳥(niǎo)我。它會(huì)進(jìn)行類文件的驗(yàn)證,確保我的Java程序沒(méi)有安全隱患! 驗(yàn)證通過(guò)了,JVM該準(zhǔn)備干啥?它需要為類中的靜態(tài)變量分配內(nèi)存并設(shè)置默認(rèn)初始值,這個(gè)過(guò)程就是準(zhǔn)備階段。 靜態(tài)變量有內(nèi)存了,JVM還需要干什么?嗯,它得解析類文件里的符號(hào)引用,像是類名、方法名、變量名等,把這些符號(hào)轉(zhuǎn)成直接引用,方便后續(xù)調(diào)用。這就是解析階段。 最后,JVM要真正幫我干活了,它要執(zhí)行類構(gòu)造器()方法的字節(jié)碼,給靜態(tài)變量復(fù)雜的初始值。這就是初始化階段。 Initialization of 類名 complete! 我的任務(wù)終于可以開(kāi)始執(zhí)行了!真不容易,還得感謝JVM這位大恩人。
面試官: 哈哈,這個(gè)解釋我喜歡!inclusion of源代碼和動(dòng)態(tài)的思考過(guò)程增加了解釋的輕松和趣味性。你這種圍繞一個(gè)場(chǎng)景作解釋的方式很形象,讓人容易理解,這在技術(shù)面試中是很重要的一點(diǎn)。
候選人: 謝謝面試官的夸獎(jiǎng)!我也覺(jué)得把一個(gè)復(fù)雜的技術(shù)問(wèn)題變成一個(gè)故事或場(chǎng)景會(huì)讓人更容易理解其中的邏輯和流程。這也是我在博客和公眾號(hào)里常用的一種講解方式,很高興面試官能夠欣賞!
面試官: 那我們繼續(xù)聊聊類加載過(guò)程中最重要的幾個(gè)類吧,什么類負(fù)責(zé) finding 和loading 操作?
候選人: 在類加載過(guò)程中,ClassLoader 類及其子類負(fù)責(zé)finding和loading操作。
面試官: 是的,ClassLoader是一個(gè)很重要的類。那么默認(rèn)的ClassLoader又有哪幾個(gè)?
候選人: 默認(rèn)有3個(gè)ClassLoader:
- Bootstrap ClassLoader 啟動(dòng)類加載器:負(fù)責(zé)加載JDK內(nèi)置的類,如rt.jar等。
- Extension ClassLoader 擴(kuò)展類加載器:負(fù)責(zé)加載JDK擴(kuò)展目錄中的jar包、以及VM指定的其他jar包。
- App ClassLoader 應(yīng)用程序類加載器:負(fù)責(zé)加載用戶自定義的類。
//JDK源碼中ClassLoader的繼承關(guān)系
public?class?ClassLoader?{
????public?ClassLoader()?{}?
????public?Class<?>?loadClass(String?name)?{...}
}
public?class?SecureClassLoader?extends?ClassLoader?{...}?
public?class?URLClassLoader?extends?SecureClassLoader?{...}?
//和類加載息息相關(guān)的其他類?
public?final?class?Class<T>?{...}??
public?class?ClassNotFoundException?extends?Exception?{...}
面試官: ClassLoader的加載順序遵循什么規(guī)則?
候選人: ClassLoader遵循父類委派模式,當(dāng)一個(gè)類加載器收到類加載請(qǐng)求時(shí),它會(huì)把這個(gè)請(qǐng)求委派給它的父類加載器去完成,依此形成一個(gè)鏈條。只有父類加載器在它的搜索范圍內(nèi)無(wú)法找到所需的類時(shí),子加載器才會(huì)嘗試自己去加載這個(gè)類。 因此,類加載的順序?yàn)?
- Bootstrap ClassLoader
- Extension ClassLoader
- App ClassLoader 如果父類可以完成類加載工作則子類不會(huì)再去加載,否則子類才會(huì)負(fù)責(zé)加載。這種委派機(jī)制可以避免重復(fù)加載,也有利于安全性。
面試官: 很好,你對(duì)Java類加載機(jī)制有很深入的理解。最后,我們聊一聊類加載過(guò)程的雙親委派模型在哪些方面帶來(lái)的好處?
候選人: 類加載雙親委派模型帶來(lái)的好處主要有兩點(diǎn):
- 避免重復(fù)加載:當(dāng)父類已經(jīng)加載了某個(gè)類時(shí),子類不會(huì)再重復(fù)加載該類,從而避免資源消耗。
- 安全性:父類加載的類被所有的子類所信任。 strs如果子類可以隨意加載,那么就可能加載一個(gè)非授權(quán)版本的類,破壞安全性。 綜上,雙親委派模型體現(xiàn)了“安全第一,不重復(fù)加載”的設(shè)計(jì)思想,這兩點(diǎn)好處使得Java類加載機(jī)制更加完備和安全。
面試官: 很好,你的回答很全面和到位。
候選人: 非常感謝面試官的指導(dǎo)。
面試官: 你的謝意我心領(lǐng)了,我們的對(duì)話也達(dá)到了我的目的。真誠(chéng)地希望這些知識(shí)能在你的工作中派上用場(chǎng)。加油!
候選人: 非常感謝面試官的鼓勵(lì)!我會(huì)努力運(yùn)用所學(xué)的知識(shí),在工作實(shí)踐中不斷進(jìn)步。也祝面試官心想事成
面試官: 好,讓我們繼續(xù)討論類加載過(guò)程中另一個(gè)重要概念:類的生命周期。什么是類的生命周期?它包括哪幾個(gè)階段?
候選人: 類的生命周期描述了一個(gè)類從被加載到被卸載的整個(gè)過(guò)程。它主要包括:
- 加載:找尋并加載類的二進(jìn)制數(shù)據(jù),將其讀入內(nèi)存,并為之創(chuàng)建一個(gè)Class對(duì)象。
- 鏈接:驗(yàn)證、準(zhǔn)備和解析。驗(yàn)證是否有正確的內(nèi)部結(jié)構(gòu),并和其他類保持一致性。準(zhǔn)備分配內(nèi)存并設(shè)置初始值。把相關(guān)的符號(hào)引用轉(zhuǎn)換為直接引用。
- 初始化:執(zhí)行類構(gòu)造器()方法的字節(jié)碼,給類的靜態(tài)變量賦予正確的初始值。
- 使用:程序使用這個(gè)類創(chuàng)建實(shí)例對(duì)象、訪問(wèn)類的靜態(tài)變量和方法等。
- 卸載:GC回收這個(gè)類的所有實(shí)例和空間。卸載該類的字節(jié)碼,并從運(yùn)行時(shí)常量池中移除這個(gè)類的符號(hào)引用。
面試官: 說(shuō)明的很詳細(xì)。類的生命周期中,有哪幾個(gè)階段會(huì)觸發(fā)類初始化?
候選人: 有三種情況會(huì)觸發(fā)類的初始化:
- 新創(chuàng)建一個(gè)該類的實(shí)例。
- 訪問(wèn)該類的靜態(tài)變量,或?yàn)殪o態(tài)變量賦值。
- 調(diào)用該類的靜態(tài)方法。 除此之外,下面這些操作不會(huì)觸發(fā)類的初始化:
- 使用一個(gè)類的名稱,如在變量聲明中使用該類的名稱。
- 使用類加載器加載一個(gè)類。
- 訪問(wèn)某個(gè)類的靜態(tài)常量。
//舉例
public?class?Test?{
????public?static?int?a?=?1;??//靜態(tài)變量,會(huì)觸發(fā)初始化
????public?static?final?int?b?=?2;???//靜態(tài)常量,不會(huì)觸發(fā)初始化
????public?static?void?method(){}???//靜態(tài)方法,會(huì)觸發(fā)初始化
}
Test?t?=?new?Test();???//創(chuàng)建實(shí)例,會(huì)觸發(fā)初始化
Test.a?=?2;???????????//訪問(wèn)靜態(tài)變量,會(huì)觸發(fā)初始化
Test.b?=?3;???????????//訪問(wèn)靜態(tài)常量,不會(huì)觸發(fā)初始化
Test.method();????????//調(diào)用靜態(tài)方法,會(huì)觸發(fā)初始化??
面試官: 說(shuō)的很清楚,舉例也很形象。那靜態(tài)代碼塊是在哪個(gè)階段執(zhí)行的?
候選人: 靜態(tài)代碼塊是在類初始化階段執(zhí)行的,位于()方法中。它優(yōu)先于構(gòu)造方法執(zhí)行,并且只執(zhí)行一次。例如:
public?class?Test?{
????static?{
????????System.out.println("靜態(tài)代碼塊執(zhí)行");
????}?
????public?Test()?{
????????System.out.println("構(gòu)造方法執(zhí)行");
????}
}
Test?t1?=?new?Test();??
//打印:
靜態(tài)代碼塊執(zhí)行??
構(gòu)造方法執(zhí)行??
Test?t2?=?new?Test();
//打印:
構(gòu)造方法執(zhí)行
因此,靜態(tài)代碼塊主要用于一次性地對(duì)類進(jìn)行初始化設(shè)置,這些設(shè)置只在類第一次被加載時(shí)執(zhí)行。它和構(gòu)造方法的不同之處在于,構(gòu)造方法在每次創(chuàng)建實(shí)例時(shí)都會(huì)執(zhí)行。
面試官: 很好,你對(duì)類的生命周期及其與類初始化的關(guān)系理解很透徹。我想你應(yīng)該可以輕松應(yīng)付與類加載相關(guān)的面試題了!
候選人: 非常感謝面試官的提問(wèn),這些關(guān)于類的生命周期和初始化階段的知識(shí)點(diǎn)對(duì)我來(lái)說(shuō)均很重要。我會(huì)不斷復(fù)習(xí)和運(yùn)用這些知識(shí),以便進(jìn)一步熟練掌握類加載機(jī)制的相關(guān)原理,從而應(yīng)對(duì)可能遇到的面試題和實(shí)際開(kāi)發(fā)中的相關(guān)問(wèn)題。
最近我在更新《面試1v1》系列文章,主要以場(chǎng)景化的方式,講解我們?cè)诿嬖囍杏龅降膯?wèn)題,致力于讓每一位工程師拿到自己心儀的offer,感興趣可以關(guān)注公眾號(hào)JavaPub追更!

