JVM:類載入 & 類載入器
System virtual machine: a.k.a.full virtualization VM. Provides a substitute for a real machine (functionality needed to execute entire OS). Allows for multiple environments which are isolated from one another yet exist on the same physical machine.
Process Virtual Machine: a.k.a.application virtual machine
Process VM runs as a normal application inside a host OS and support a single process, is created when that process is started and destroyed when it exits.
Process VM’s purpose is to provide aplatform-independent programming environmentthat abstracts away details of the underlying hardware and operating system and allows a program to execute in the same way on any platform.
e.g. JVM; Parrot virtual machine; .Net Framework.
*使用工具:
Source Insight -檢視openjdk原始碼
clion - 編寫底層程式
idea / netbeans - 單步除錯jdk
hsdb
類載入
Klass
Klass:(存在於元空間,)Java的每個類的物件在JVM中都有一個對應的Klass類例項,用於儲存類的元資訊(e.g.常量池,屬性資訊,方法資訊)。
Klass的繼承結構
MasterspaceObj
|-Metadata
|-Klass
|-InstanceKlass
|-InstanceMirrorKlass
|-InstanceRefKlass
|-InstanceClassLoaderKlass
|-ArrayKlass
|-TyperArrayKlass
|-ObjArrayKlass
InstaneKlass:表示普通(非陣列)Java類。類載入器將.class檔案載入進系統,將.class檔案解析生成類的元資訊,儲存在InstanceKlass中。子類包括InstanceMirrorKlass、InstanceRefKlass和InstanceClassLoaderKlass。
InstanceMirrorKlass:表示Java程式碼中的java.lang.Class類,儲存在堆區。
InstanceRefKlass:表示java.lang.ref.Reference類的子類。
InstanceClassLoaeder:用於遍歷某個載入器載入的類。
Java中的陣列不是靜態資料型別(e.g. JVM內建的8種資料型別),是動態資料型別(i.e.在執行期生成的)。
ArrayKlass:儲存陣列類的元資訊。
TyperArrayKlass:表示基本型別的陣列。
ObjArrayKlass:表示引用型別的陣列。
實驗
證明java陣列是動態資料型別
->java main方法中new一個int/物件陣列,編譯執行
->IDEA使用外掛jclasslib檢視位元組碼(idea->view->show bytecode with Jclasslib),main中顯示newarray/anewarray,對應位元組碼手冊中含義:“建立一個原始型別/引用型陣列並將其引用至壓入棧頂”。
檢視java類對應的klass
->HSDB -> Tools/Class Browser->找到目標類名即可找到對應記憶體地址
->HSDB->Tools/Inspector->輸入記憶體地址->可檢視java類在記憶體中對應的klass類
or
->while true維持程式執行,terminal輸入jps –l獲取當前執行程序id;
->HSDB->file/attach to hotspot process->輸入目標程序ID
->選中main執行緒,工具欄第二個按鈕檢視執行緒堆疊->可檢視java物件底層記憶體地址
->複製記憶體地址->HSBD->tool/inspector->可檢視java物件底層的實現類
HSDB attach後記得detach。
類載入的過程
載入--可以隨便使用任何語言實現類載入器,只要能夠達到這三個效果。
->通過類的全限定名獲取儲存該類的class檔案(沒有指明必須從哪獲取);
->解析成執行時資料(instanceKlass例項),存放在方法區;
->在堆區生成該類的Class物件(instanceMirrorKlass例項)。
JVM載入類是懶載入模式。--根載入器載入jar檔案時並沒有把其中所有的類都進行載入,而是隻載入了一部分(預載入模式,只先載入常用的String,Thread,Integer等類)。
類載入的時機?--主動使用時
1)new, getstatic, putstatic, invokestatic位元組碼i.e. java程式碼中使用new關鍵字例項化物件、讀取或設定類的靜態欄位(final修飾的常量除外)、呼叫類的靜態方法
2)反射
3)初始化子類時會去載入其父類
4)啟動類(main函式所在類)
5)當使用JDK1.7動態語言支援時,如果一個java.lang.invoke.MethodHandle例項最後的解析結果REF_getStatic、REF_putStatic、REF_invokeStatic的方法控制代碼,並且這個方法控制代碼所對應的類沒有進行過初始化,則需要先觸發其初始化。
從哪載入?-因為沒有指明從哪獲取class檔案,可採用的思路:
1)從壓縮包中讀取e.g. jar, war
2)從網路中獲取e.g. Web Applet
3)動態生成e.g.動態代理、CGLIB
4)由其他檔案生成e.g. JSP
5)從資料庫讀取
6)從加密檔案讀取
驗證–檢查klass檔案是否符合規範,判斷版本, jvm能否正常執行klass,…etc.
1.檔案格式驗證
2.元資料驗證
3.位元組碼驗證
4.符號引用驗證
//參考《深入理解java虛擬機器》
準備
為靜態變數分配記憶體和賦初值。例項變數沒有賦初值一說,而是在建立物件時完成賦值。
例外:如果被Final修飾,編譯時會給新增ConstantValue屬性,準備階段直接完成賦值,沒有賦初值步驟。
不同資料型別對應不同的初值:
解析–間接引用轉為直接引用
*常量池:包括靜態常量池(class檔案常量池)、執行時常量池(可在HSDB中檢視)和字串常量池(StringTable)。
間接引用a.k.a.符號引用:指向執行時常量池的引用
直接引用:記憶體地址
1.類或介面的解析
2.欄位解析
3.方法解析
4.介面方法解析
解析後的資訊儲存在ConstantPoolCache類例項中。
何時解析?--思路有:
1)載入階段解析常量池時
2)用時// openjdk採用,在執行特定位元組碼指令(e.g. anewarray, checkcast, getfield, …)前進行解析。
初始化–執行靜態程式碼段,完成靜態變數的賦值。
java程式碼中定義一個static屬性(靜態欄位、靜態程式碼段),位元組碼層面就會生成clint方法(只有一個)。
clint方法(i.e.位元組碼中生成的靜態塊)中語句順序跟定義靜態屬性的java程式碼的編寫順序是保持一致的。
E.g. java程式碼:
public static int a=10; public static int b=10;
位元組碼:
實驗
證明final成員沒有賦初值而是直接賦值
->演示類中宣告成員static final int a=10, static int b=10;
->檢視位元組碼,a中有屬性ConstantValue,代表沒有賦初值,準備階段就完成賦值。(b在準備階段被賦初值0)
檢視常量池
-> idea terminal切換到classes目錄下, javap –verbose物件全限定名(全限定名可在ide中選中類名右鍵copy reference得),輸出的Constant pool:部分表示靜態常量池
e.g.靜態常量池中Class對應#32,翻閱下方對應當前類名字串。’#32’即為一個符號引用(指向常量池的引用)。
->執行程式,HSDB attach到目標程序,class browser點選目標物件,下方可檢視動態常量池
e.g.動態常量池中Class不再指向常量池,而是指向記憶體地址@0x000….,即為一個直接引用。
初始化實驗1
public class Test { public static void main(String[] args) { TestA obj=TestA.getInstance(); System.out.println(TestA.val1); System.out.println(TestA.val2); } } class TestA { public static int val1; public static val2=1; public static TestA instance=new TestA(); TestA() { val1++; val2++; } public static TestA getInstance() { return instance; } }
輸出:12
原因:初始化時執行靜態程式碼段,val1賦初值為0,val1賦值為1;執行建構函式後都+1;輸出1 2.
初始化實驗2 //將static val2定義移到構造方法後
public class Test { public static void main(String[] args) { TestA obj=TestA.getInstance(); System.out.println(TestA.val1); System.out.println(TestA.val2); } } class TestA { public static int val1; public static TestA instance=new TestA(); TestA() { val1++; val2++; } public static val2=1; public static TestA getInstance() { return instance; } }
輸出:11
原因:生成的靜態塊中語句順序跟定義靜態屬性的java程式碼的編寫順序是保持一致的,所以val2經過建構函式後被定義語句覆蓋回1;
程式的執行順序: 1) clint方法2)預設構造方法(執行完++後val1=1,val2=1);靜態塊(val2又被賦值為1)。
載入試驗1
public class Test { public static void main(String[] args) { System.out.printf(TestB.str); } } class TestA { public static String str=”A str”; static { System.out.println(“A Static Block”); } } class TestB extends TestA { static { System.out.println(“B Static Block”); } }
輸出:Astatic Astr
原因:A是B的父類,會被主動載入;B沒有被使用,不會被載入
載入試驗2
public class Test { public static void main(String[] args) { System.out.printf(new TestB().str); //new了B物件 } } class TestA { public String str=”A str”;//去掉static static { System.out.println(“A Static Block”); } } class TestB extends TestA { static { System.out.println(“B Static Block”); } }
輸出:Astatic Bstatic Astr
原因:AB都被使用,都會被載入(主動使用子類,就是間接在主動使用父類)
載入試驗3
public class Test { public static void main(String[] args) { System.out.printf(new TestB().str); } } class TestA { static { System.out.println(“A Static Block”); } } class TestB extends TestA { public String str=”A str”;//str從A移到B static { System.out.println(“B Static Block”); } }
輸出:Astatic Bstatic Astr
原因:同上
載入試驗4
public class Test { public static void main(String[] args) { System.out.printf(testB.str); } } class TestA { static { System.out.println(“A Static Block”); } } class TestB extends TestA { public static String str=”B str”; //static成員 static { System.out.println(“B Static Block”); } }
輸出:Astatic Bstatic Bstr
原因:靜態欄位在子類裡,子類會被載入
載入試驗5
public class Test { public static void main(String[] args) { System.out.println(TestA.str); } } class TestA { public static final String str=”A Str”; static { System.out.println(“A Static Block”); } }
輸出:AStr
原因:雖然AStr在A類裡,但是是被final修飾的常量,此常量被寫入到Test類的常量池中
載入試驗6
public class Test { public static void main(String[] args) { System.out.println(TestA.uuid); } } class TestA { public static final String uuid=UUID.randomUUID().toString(); static { System.out.println(“A Static Block”); } }
輸出:Astatic uuid
原因:雖然uuid是final修飾,但randomUUID().toString()是動態執行的,uuid需要動態生成,不能寫入到Test的常量池。所以類A會被載入。
載入試驗7
public class Test2 { static { System.out.println(“Test2 Static Block”); } public static void main(String[] args) throws ClassNotFoundException { Class<?> clazz=Class.forName(“com.xxxx.Test1”); } }
輸出:Test2Static Test1Static
原因:因為反射,類12都會被載入
載入試驗8
public class Test { public static void main(String[] args) { System.out.printf(B.str); } } class A { public static String str=”str”; static { System.out.println(“A Static Block”); } } class B extends A { static { str+=”###”; System.out.println(“B Static Block”); } }
輸出:Astatic str
原因:JVM先判斷是否載入,後面才會有初始化動作發生。案例中B的內部沒有任何東西被使用,所以沒有載入B,B的靜態塊不會被執行。
讀取靜態變數的底層實現 涉及InstanceKlass, instanceMirrorKlass, ConstantPoolCache
實驗證明靜態屬性儲存在映象類中
public class Test { public static void main(String[] args) { System.out.printf(B.str); while (true) {} } } class A { public static String str=”A str”; static { System.out.println(“A Static Block”); } } class B extends A { static { System.out.println(“B Static Block”); } }
輸出:Astatic Astr
->執行,查出程序ID,在HSDB中attach
-> HSDB classbrowser找到類A的記憶體地址,輸入到inspector
->可以在inspector類A中找到靜態屬性str是儲存在oop Klass: java.mirror中。說明jdk8靜態屬性是儲存在映象類(instanceMirrorKlass)中的。(而不是儲存在instanceKlass, jdk6之前是)
->同樣操作在inspector類B的oop Klass:java.mirror中並沒有找到靜態屬性str,說明靜態屬性str只存放在父類A。
-既然靜態屬性str存放在父類A中,main中呼叫B.str是怎麼找到它的?
-兩種實現思路:
1)先從子類B的映象類中取,如果有直接返回,沒有則沿著繼承鏈往上找;-- O(n)
2)藉助另外的資料結構,使用K-V格式儲存。-- O(1)。
Hotspot採用的就是思路2,藉助另外的資料結構ConstantPoolCache,常量池類ConstantPool有屬性_cache指向該結構,每條資料對應一個類ConstantPoolCacheEntry。
*ConstantPoolCache:用於儲存某些位元組碼指令所需的解析(resolve)好的常量項,例如給[get|put]static, [get|put]field, invoke[static|special|virtual|interface|dynamic]等指令對應的常量池項用。
ConstantPoolCacheEntry的獲取?
參考\openjdk\hotspot\src\share\vm\oop\cpCache.hpp,通過ConstantPoolCache的地址加上偏移量
類載入器
JVM的類載入器包含兩種型別:
1)由C++編寫的;--啟動類載入器(Bootstrap Class Loader)
2)由Java編寫的--其他繼承java.lang.ClassLoader的類載入器
JVM也支援自定義類載入器。
各種類載入器之間邏輯上的父子關係不是真正的父子關係,沒有直接從屬關係。
啟動類載入器
啟動類載入器:JVM將C++處理類的一套邏輯定義為啟動類載入器。啟動類載入器沒有實體。
因為由C++編寫,無法被Java程式呼叫,在Java程式中顯示null。
啟動類載入器的載入路徑
URL[] urLs = Launcher.getBootstrapClassPath().getURLs(); for (URL urL : urLs) { System.out.println(urL); }
openjdk原始碼
JavaMain中呼叫了LoadMainClass,啟動類載入器就是在這時載入的。
Openjdk/jdk/src/share/bin/java.c / JavaMain()
JavaMain(void * _args) { … mainClass = LoadMainClass(env, mode, what); … }
LoadMainClass中需要先找到Launcherhelper類。啟動類載入器所做的事情就是載入類”sun/launcher/LauncherHelper”,checkAndLoadMain就是在LauncherHelper類裡面。
Openjdk/jdk/src/share/java.c / GetLauncherHelperClass()
GetLauncherHelperClass(JNIEnv *env) { … NULL_CHECK0(helperClass = FindBootStrapClass(env, "sun/launcher/LauncherHelper")); … }
GetLauncherHelperClass主要呼叫FindBootStrapClass。FindBootStrapClass中用GetProcessAddress呼叫JVM動態連結庫的JVM_FindClassFromBootLoader方法。
openjdk/jdk/src/windows/bin/java_md.c / FindBootStrapClass()
jclass FindBootStrapClass(JNIEnv *env, const char *classname) { … findBootClass = (FindClassFromBootLoader_t *)GetProcAddress(hJvm, "JVM_FindClassFromBootLoader"); … }
找到LauncherHelper類後,通過JNI執行LauncherHelper類的checkAndLoadMain方法。
用於載入main class(main方法所在的類),三種類載入器的父子鏈(->啟動擴充套件類載入器->應用類載入器)也是在這次呼叫中完成的。
LoadMainClass返回的result其實就是呼叫checkAndLoadMain的結果– main class。
Openjdk/jdk/src/share/bin/java.c / LoadMainClass()
LoadMainClass(JNIEnv *env, int mode, char *name) { … jclass cls = GetLauncherHelperClass(env); … NULL_CHECK0(mid = (*env)->GetStaticMethodID(env, cls, "checkAndLoadMain", "(ZILjava/lang/String;)Ljava/lang/Class;")); … }
從checkAndLoadeMain開始都是java程式碼。checkAndLoadMain方法檢查了執行模式,如果是class就直接命名,如果是jar包則從jar包中找,其他則報錯。然後呼叫scloader.loadClass方法獲得main class來返回。
openjdk/jdk/src/share/classes/sun/launcher/LauncherHelper.java / checkAndLoadMain()
public static Class<?> checkAndLoadMain(boolean printToStderr, int mode, String what) { … switch (mode) { case LM_CLASS: cn = what; break; case LM_JAR: cn = getMainClassFromJar(what); break; default: // should never happen throw new InternalError("" + mode + ": Unknown launch mode"); } … mainClass = scloader.loadClass(cn); … }
scloader是由ClassLoader.getSystemClassLoader()得到的,其中呼叫了initSystemClassLoader方法。
Openjdk/jdk/src/share/classes/java/lang /ClassLoader.java / getSystemClassLoader()
public static ClassLoader getSystemClassLoader() { initSystemClassLoader(); … }
initSystemClassLoader中呼叫了Launcher.getLauncher方法。
Openjdk/jdk/src/share/classes/java/lang /ClassLoader.java / initSystemClassLoader
private static synchronized void initSystemClassLoader() { … sun.misc.Launcher l = sun.misc.Launcher.getLauncher(); … }
Launcher建構函式中初始化了ExtClassLoader,再以ext為引數初始化appClassLoader。Ext其實就是parent。
執行緒上下文類載入器contextClassLoader也是在這時賦值的。
openjdk/jdk/src/share/classes/sun/misc/Launcher.java
public class Launcher { … private static Launcher launcher = new Launcher(); … public static Launcher getLauncher() { return launcher; } public Launcher() { … // Create the extension class loader extcl = ExtClassLoader.getExtClassLoader(); … // Now create the class loader to use to launch the application loader = AppClassLoader.getAppClassLoader(extcl); … Thread.currentThread().setContextClassLoader(loader); } … }
從getAppClassLoader()和其中呼叫的AppClassLoader建構函式可看出傳入的extcl引數為parent。
openjdk/jdk/src/share/classes/sun/misc/Launcher.java /AppClassLoader
static class AppClassLoader extends URLClassLoader { public static ClassLoader getAppClassLoader(final ClassLoader extcl) throws IOException { … return new AppClassLoader(urls, extcl); } AppClassLoader(URL[] urls, ClassLoader parent) {…} }
為什麼Ext的parent是null?
從ExtClassLoader建構函式看出其super建構函式傳入的就是null,而ExtClassLoader建構函式的super對應的形參就是parent。
openjdk/jdk/src/share/classes/sun/misc/Launcher.java /ExtClassLoader
static class ExtClassLoader extends URLClassLoader { public static ExtClassLoader getExtClassLoader() throws IOException { … return new ExtClassLoader(dirs); } public ExtClassLoader(File[] dirs) throws IOException { super(getExtURLs(dirs), null, factory); … } … }
openjdk/jdk/share/classes/java/net/URLClassLoader.java
URLClassLoader(URL[] urls, ClassLoader parent, AccessControlContext acc) {…}
順序:jvm的目的是要去載入main所在類->啟動boot載入器->啟動ext-載入器>啟動app載入器->通過app載入器去載入main class。
所以這不是繼承上的父子關係,而是載入鏈邏輯上的父子關係。邏輯上的父子關係目的就是為了雙親委派。
擴充套件類載入器
擴充套件類載入器的載入路徑是通過向System.getProperty()傳入引數”java.ext.dirs”
String[] urls = System.getProperty(“java.ext.dirs”).split(“:”); for (String url : urls) System.out.println(url); ClassLoader classLoader = ClassLoader.getSystemClassLoader().getParent(); URLClassLoader urlClassLoader = (URLClassLoader) classLoader; URL[] urls = urlClassLoader.getURLs(); for (URL url : urls) System.out.println(url);
向System.getProperty()傳入引數”java.ext.dirs”的做法也可以在openjdk原始碼中看到:
openjdk/jdk/src/share/classes/sun/misc/Launcher.java /ExtClassLoader
public ExtClassLoader(File[] dirs) throws IOException { super(getExtURLs(dirs), null, factory); … } private static File[] getExtDirs() { String s = System.getProperty("java.ext.dirs"); … }
不同的類載入器載入同一個類,相等嗎?
不相等。方法區是按照類載入器進行分開儲存的。每個類載入器在方法區裡都有一塊獨立的區域,雖然載入的是同一份檔案,但是不會在同一個空間裡。
同一個類載入器載入同一個檔案多次,實際上會載入幾次?
一次。因為載入前會(根據全限定名)去判斷空間裡是否已經有這個類。
雙親委派
雙親委派:需要查詢某個類時,先判斷在當前類載入器是否已經載入(能在其空間中找到),如果已經載入則直接返回,沒有則向上委託給其父類載入器。
*系統已載入的class資訊儲存在SystemDictionary類中。
e.g.查詢某個類
->判斷當前最下層的自定義的類載入器是否已載入該類,是則直接返回,否則往上委託給父類AppClassLoader;
->判斷在AppClassLoader中是否已經載入,是則直接返回,否則再往上委託給父類ExtClassLoader;
-> …委託給BootstrapClassLoader…
->如果BootstrapClassLoader也沒有載入直接報錯
侷限性:無法做到不委派或向下委派
e.g.資料庫需要實現的driver介面是由啟動類載入器載入。而第三方資料(如mysql)的相關實現類需要由應用類載入器載入,啟動類載入器不能載入,需要向下委派。
什麼叫打破雙親委派?
兩種思路。a)不委派–只用當前類載入器去載入–實現方式:自定義類載入器
或b)向下委派(SPI機制中的一部分)
SPI:一種服務發現機制,通過在ClassPath路徑下的META-INF/services資料夾查詢檔案,自動載入檔案裡所定義的類
e.g. SPI Demo(該案例不算打破雙親委派,因為所有類都是啟動類載入器載入的)
PayService是自定義的一個介面,有兩個實現AlipayService和WxpayService。
Public interface PayService { void pay(); }
main中使用執行緒上下文載入器載入PayService類。
public static void main(String[] args) { ServiceLoader<PayService> services=ServiceLoader.load(PayService.class); for (PayService service : services) service.pay(); }
通過介面呼叫的pay方法呼叫的是哪一個實現類是看pom.xml/<dependencies>/<artifactId>中指定載入的是哪一個模組。
Pom.xml (gateway-main)
<dependencies> <dependency> … <artifactId>pay-wx</artifactId> … </dependency> … </dependencies>
每個實現類底下的/src/main/resources/META-INF.services/目錄下的檔案中指定了實現類的全限定名。SPI從而找到需要載入的實現類。
/pay-wx/src/main/resources/META-INF.services/com.luban.common.service.PayService
com.luban.pay.AlipayService
實現向下委派?
需要使用ServiceLoader。
e.g. Driver中的SPI機制。JDBI的底層實現用到serviceloader,也是一種SPI。driver通過向下委派來打破雙親委派。
ServiceLoader<Driver> loadedDrivers=ServiceLoader.load(Driver.class); Iterator<Driver> driversIterator=loadedDrivers.iterator();
執行緒上下文類載入器
ServiceLoader的底層是由執行緒上下文類載入器ContextClassLoader實現的,在SPI中向下委派中有應用。
ContextClassLoader可通過Thread.currentThread().setContextClassLoader()進行設定。
在checkAndLoadMain時已經進行設定,預設為AppClassLoader。
openjdk/jdk/src/share/classes/sun/misc/Launcher.java
public Launcher() { … // Now create the class loader to use to launch the application loader = AppClassLoader.getAppClassLoader(extcl); … Thread.currentThread().setContextClassLoader(loader); }
自定義類載入器
如何實現自定義類載入器?
extends ClassLoader類,重寫findClass方法。
實驗1:自定義載入器重寫findClass時返回null,能否載入成功
public class ClassLoader1 extends ClassLoader { public void main(String[] args) throws Exception { Classloader1 classloader1=new Classloader1(); Class<?> clazz=classloader1.loadClass(“com.experiment.classloader.A”); System.out.println(“clazz hashcode: “+clazz.hashCode()); } @Override protected Class<?> findClass(String className) throws ClassNotFoundException { return null; } }
結果:可以載入
原因:雙親委派。可通過System.out.println(clazz1.getClassLoader())打印出sun.misc.Launcher$AppClassLoader證明clazz1是由AppClassLoader載入的。
實驗2
public class ClassLoader1 extends ClassLoader { public void main(String[] args) throws Exception { Classloader1 classloader1=new Classloader1(), classloader2=new Classloader1(); Class<?> clazz1=classloader1.loadClass(“com.experiment.classloader.A”); Class<?> clazz2=classloader2..loadClass(“com.experiment.classloader.A”); System.out.println(clazz1==clazz2) } @Override protected Class<?> findClass(String className) throws ClassNotFoundException {…} }
結果:true
原因:用同一個classLoader類載入同一個類,得到的Class是同一個類。可通過System.out.println(clazz1.hashCode())證明clazz1和clazz2的hashCode是一樣的。
自定義類載入器如何打破雙親委派
classLoadder原始碼中:
Class(String)底層了呼叫loadClass(String, boolean)
public Class<?> loadClass(String name) throws ClassNotFoundException { return this.loadClass(name, false); }
loadClass(String, Boolean)中的雙親委派邏輯
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { … Class<?> c = this.findLoadedClass(name); if (c == null) { //判斷是否已經載入 try { if (this.parent! = null) c = this.parent.loadClass(name, false); //向上委派 else c = this.findBootstrapClassOrNull(name); } catch (ClassNotFoundException) {} } … if (resolve) {//判斷有無解析,進行解析 this.resolveClass(c); } return c; }
可通過重寫loadClass(String, Boolean)打破雙親委派:對指定包下的類的載入做不委派處理
@Override protected Class<?> loadClass(String name, Boolean resolve) throws ClassNotFoundException { … if (c == null) { if (name.startsWith(“com.experiment”)) c = findClass(name); else c = this.getParent().loadClass(name); } }
沙箱安全
checkAndLoadMain底層有呼叫到initSystemClassLoader。initClassLoader中所做的AccessController.doPrivileged判斷就是一種沙箱安全機制。
Openjdk/jdk/src/share/classes/java/lang /ClassLoader.java / initSystemClassLoader
private static synchronized void initSystemClassLoader() { … scl = AccessController.doPrivileged( new SystemClassLoaderAction(scl)); … }
實驗:沙箱安全
在自創java.lang包下定義String類,在main中呼叫String的方法會報錯。
public class String { public static void main(String[] args) { String.show(); } public static void show() { System.out.println(“String show function”); } }
結果:報錯“在類中找不到main方法”
原因:沙箱安全防止打破雙親委派修改系統類,保護核心類庫。