JVM 之 類的裝載機制
看到網上有一道面試題:能不能裝載自定義的 java.lang.String?
答案是否定的,我們能自定義一個java.lang.String,但是載入不進來。
我相信很多人在網上看到這樣的答案“可以,但在應用的時候,需要用自己的類載入器去載入”。這個回答是錯誤的,現在我們來分析一下jvm在裝載一個類的時候,是如何進行的。
雙親委派模型
當虛擬機器接受到一個類的載入請求時,它將這個載入請求委派給父類載入器進行載入,只有當父類載入器自己無法完成載入請求時,子類載入器才會嘗試自己載入。
那麼系統自帶的載入器有哪些呢?
1.啟動類載入器BootstrapClassLoader:
是嵌在JVM核心中的載入器,該載入器是用C++語言寫的,主要負載載入JAVA_HOME/lib下的類庫,啟動類載入器無法被應用程式直接使用。
2.擴充套件類載入器Extension ClassLoader:
該載入器器是用JAVA編寫,且它的父類載入器是Bootstrap,是由sun.misc.Launcher$ExtClassLoader實現的,主要載入JAVA_HOME/lib/ext目錄中的類庫。開發者可以這幾使用擴充套件類載入器。
3.統類載入器App ClassLoader:
系統類載入器,也稱為應用程式類載入器,負責載入應用程式classpath目錄下的所有jar和class檔案。它的父載入器為Ext ClassLoader。
各個類載入器之間是組合關係,並非繼承關係。
雙親委託模式的優點
1.避免重複載入。當父親已經載入了該類的時候,就沒有必要子ClassLoader再載入一次。
2.安全性。如果不使用這種委託模式,那我們就可以隨時使用自定義的String來動態替代java核心api中定義型別,這樣會存在非常大的安全隱患。
重寫String
回到上面的面試題,我重寫了一個java.lang.String的類
package java.lang;
public class String {
public static void main(String[] args) {
System.out .println("Hello String");
}
}
執行之後會拋一個異常:
錯誤: 在類 java.lang.String 中找不到主方法, 請將主方法定義為:
public static void main(String[] args)
因載入某個類時,優先使用父類載入器載入需要使用的類。如果我們自定義了java.lang.String這個類,載入該自定義的String類,該自定義String類使用的載入器是AppClassLoader。
根據優先使用父類載入器原理,AppClassLoader載入器的父類為ExtClassLoader,所以這時載入String使用的類載入器是ExtClassLoader,但是類載入器ExtClassLoader在jre/lib/ext目錄下沒有找到String.class類。
然後使用ExtClassLoader父類的載入器BootStrap,父類載入器BootStrap在JRE/lib目錄的rt.jar找到了String.class,發現已經載入過了,於是不會再載入我們自定義的類。
打破雙親委託機制
有些人看到這,就會想,那我們自定義一個類載入器,但是不實現雙親委託機制就好了,強行載入,就算重複了也不管。但是你會發現也不會載入成功,具體就是因為針對java.*開頭的類,jvm的實現中已經保證了必須由bootstrp來載入。並且這個方法被final修飾的,是改不了的。