java
classloader
認識ClassLoader
2018/09/03 10:58:28
0
818
認識ClassLoader
簡介 |
認識java最基礎class載入運行原理 |
作者 |
林政儀 |
JVM預設ClassLoader模型

Class與ClassLoader基本特點:
- jvm啟動後會建立三個基礎ClassLoader:
(a) BootStrapClassLoader: 第一個建立的ClassLoader, 負責載入jre相關classes、bin/*jar
*可由系統參數"sun.boot.class.path"設定或取得
(b) ExtClassLoader: 第二建立的ClassLoader, 負責載入jer的lib/ext/*.jar
*可由系統參數"sun.boot.class.path"設定或取得
(c) AppClassLoader: 第三建立的ClassLoader, 負責載入application相關
*可由系統參數"java.class.path"設定或取得, 或透過-cp設定 - 每個ClassLoader都有parentClassLoader, 該parentClassLoader並非繼承關係, 而是邏輯關係 (下面將可透過source code了解)
- 若ClassLoader.getParent()返回null, 看起來像是沒有parentClassLoader, 但其實是程式邏輯上控制由"BootStrapClassLoader"擔任該CLassLoader之parentClassLoader
- 若同個.class檔案由不同ClassLoader載入, 將視為不同的Class<?>, 意即若將ClassLoader(A)載入之test.class實例派給ClassLoader(B)的ClassType將會拋出java.lang.ClassCastException
- ClassLoader最重要的兩個載入class方法:
loadClass(): 最基礎載入方法, 內部邏輯為優先調用parentClassLoader的loadClass(), 若載不到class才會呼叫findClass()
findClass(): 為protected方法, 繼承ClassLoader基本上都會Override此方法, 實作載入class方法 - 預設程式運作時將會使用Thread.currentThread().getContextClassLoader()的ClassLoader來載入class,
因此透過Thread.currentThread().setContextClassLoader(...), 將可抽換預設的ClassLoader,
而由此Thread fork 的其他Thread將會沿用此ContextClassLoader。 - ClassLoader被GC條件為由此ClassLoader載入的所有class實例已被GC,以及相關static field不再被其他程式參照。
測試程式
AeryPractice_ClassLoader.java
package org.aery.practice.classloader;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
public class AeryPractice_ClassLoader {
public static void main(String[] args)
throws ClassNotFoundException, IOException, InstantiationException, IllegalAccessException {
printInfo();
testDifferentClassLoader();
}
/** 測試由不同ClassLoder載入相同.class之狀況 */
private static void testDifferentClassLoader()
throws ClassNotFoundException, IOException, InstantiationException, IllegalAccessException {
URL[] urls = new URL[] { //
new URL("file:/C:\\AeryLocal\\Work\\ws-aery\\test\\bin\\") //
};
String loadClassQualifiedName = "org.aery.practice.classloader.TestClass";
Class<?> loadTestClass1Type;
try (AeryCustomClassLoader aeryCustomClassLoader1 = new AeryCustomClassLoader(urls)) {
loadTestClass1Type = aeryCustomClassLoader1.loadClass(loadClassQualifiedName);
}
Class<?> loadTestClass2Type;
try (AeryCustomClassLoader aeryCustomClassLoader1 = new AeryCustomClassLoader(urls)) {
loadTestClass2Type = aeryCustomClassLoader1.loadClass(loadClassQualifiedName);
}
System.out.println();
System.out.println("loadTestClass1.equals(loadTestClass2) = " + loadTestClass1Type.equals(loadTestClass2Type));
Object loadTestClass1 = loadTestClass1Type.newInstance();
TestClass testClass; // 因為這裡出現TestClass此class, 因此將由Thread.currentThread().getContextClassLoader()載入
testClass = (TestClass) loadTestClass1;
testClass.print();
}
/** 列印預設的三個ClassLoader階層關係與搜尋的path */
private static void printInfo() {
Thread currentThread = Thread.currentThread();
ClassLoader contextClassLoader = currentThread.getContextClassLoader();
System.out.println();
printAllParentClassLoader(contextClassLoader, 1);
System.out.println();
printAllURLClassLoaderLoadURL((URLClassLoader) contextClassLoader);
printClassPathSystemProperty("sun.boot.class.path");
printClassPathSystemProperty("java.ext.dirs");
printClassPathSystemProperty("java.class.path");
}
private static void printClassPathSystemProperty(String propertyKey) {
String propertyPath = System.getProperty(propertyKey);
String[] paths = propertyPath.split(";");
System.out.println();
System.out.println(propertyKey);
for (String path : paths) {
System.out.println(" " + path);
}
}
public static void printAllParentClassLoader(ClassLoader classLoader, int offset) {
ClassLoader parentClassLoader = classLoader.getParent();
if (parentClassLoader != null) {
printAllParentClassLoader(parentClassLoader, offset + 1);
}
Class<? extends ClassLoader> classLoaderType = classLoader.getClass();
String msgFormat = String.format("%d %s", offset, classLoaderType.getName());
System.out.println(msgFormat + "\n" + fetchExtendsHierarchy(classLoaderType));
}
public static String fetchExtendsHierarchy(Class<?> classType) {
String title = " ";
Class<?> superClassType = classType.getSuperclass();
if (Object.class.equals(superClassType)) {
return title + classType.getName();
}
String rootsHierarchy = fetchExtendsHierarchy(superClassType);
return rootsHierarchy + "\n" + title + classType.getName();
}
public static void printAllURLClassLoaderLoadURL(URLClassLoader urlClassLoader) {
ClassLoader parentClassLoader = urlClassLoader.getParent();
if (parentClassLoader != null) {
printAllURLClassLoaderLoadURL((URLClassLoader) parentClassLoader);
}
System.out.println(urlClassLoader.getClass().getName());
URL[] loadUrls = urlClassLoader.getURLs();
for (URL url : loadUrls) {
System.out.println(" " + url);
}
}
}
AeryCustomClassLoader.java
package org.aery.practice.classloader;
import java.net.URL;
import java.net.URLClassLoader;
public class AeryCustomClassLoader extends URLClassLoader {
public AeryCustomClassLoader(URL[] urls) {
super(urls);
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
if ("org.aery.practice.classloader.TestClass".equals(name)) {
return super.findClass(name);
} else {
return super.loadClass(name, resolve);
}
}
}
TestClass.java
package org.aery.practice.classloader;
public class TestClass {
public void print() {
System.out.println(TestClass.class.getClassLoader());
}
}
/** 列印預設的三個ClassLoader階層關係與搜尋的path */
printInfo();
2 sun.misc.Launcher$ExtClassLoader
(繼承自URLClassLoder, 其parentClassLoader為BootStrapClassLoader)
java.lang.ClassLoader
java.security.SecureClassLoader
java.net.URLClassLoader
sun.misc.Launcher$ExtClassLoader
1 sun.misc.Launcher$AppClassLoader
(繼承自URLClassLoder, 其parentClassLoader為ExtClassLoader)
java.lang.ClassLoader
java.security.SecureClassLoader
java.net.URLClassLoader
sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$ExtClassLoader
(列印ExtClassLoader給URLClassLoader的搜索路徑)
file:/C:/Program%20Files/ojdkbuild/java-1.8.0-openjdk-1.8.0.171-1/jre/lib/ext/access-bridge-64.jar
file:/C:/Program%20Files/ojdkbuild/java-1.8.0-openjdk-1.8.0.171-1/jre/lib/ext/cldrdata.jar
file:/C:/Program%20Files/ojdkbuild/java-1.8.0-openjdk-1.8.0.171-1/jre/lib/ext/dnsns.jar
file:/C:/Program%20Files/ojdkbuild/java-1.8.0-openjdk-1.8.0.171-1/jre/lib/ext/jaccess.jar
file:/C:/Program%20Files/ojdkbuild/java-1.8.0-openjdk-1.8.0.171-1/jre/lib/ext/localedata.jar
file:/C:/Program%20Files/ojdkbuild/java-1.8.0-openjdk-1.8.0.171-1/jre/lib/ext/nashorn.jar
file:/C:/Program%20Files/ojdkbuild/java-1.8.0-openjdk-1.8.0.171-1/jre/lib/ext/sunec.jar
file:/C:/Program%20Files/ojdkbuild/java-1.8.0-openjdk-1.8.0.171-1/jre/lib/ext/sunjce_provider.jar
file:/C:/Program%20Files/ojdkbuild/java-1.8.0-openjdk-1.8.0.171-1/jre/lib/ext/sunmscapi.jar
file:/C:/Program%20Files/ojdkbuild/java-1.8.0-openjdk-1.8.0.171-1/jre/lib/ext/sunpkcs11.jar
file:/C:/Program%20Files/ojdkbuild/java-1.8.0-openjdk-1.8.0.171-1/jre/lib/ext/zipfs.jar
sun.misc.Launcher$AppClassLoader
(列印AppClassLoader給URLClassLoader的搜索路徑)
file:/C:/Program%20Files/ojdkbuild/java-1.8.0-openjdk-1.8.0.171-1/jre/lib/resources.jar
file:/C:/Program%20Files/ojdkbuild/java-1.8.0-openjdk-1.8.0.171-1/jre/lib/rt.jar
file:/C:/Program%20Files/ojdkbuild/java-1.8.0-openjdk-1.8.0.171-1/jre/lib/jsse.jar
file:/C:/Program%20Files/ojdkbuild/java-1.8.0-openjdk-1.8.0.171-1/jre/lib/jce.jar
file:/C:/Program%20Files/ojdkbuild/java-1.8.0-openjdk-1.8.0.171-1/jre/lib/charsets.jar
file:/C:/Program%20Files/ojdkbuild/java-1.8.0-openjdk-1.8.0.171-1/jre/lib/ext/access-bridge-64.jar
file:/C:/Program%20Files/ojdkbuild/java-1.8.0-openjdk-1.8.0.171-1/jre/lib/ext/cldrdata.jar
file:/C:/Program%20Files/ojdkbuild/java-1.8.0-openjdk-1.8.0.171-1/jre/lib/ext/dnsns.jar
file:/C:/Program%20Files/ojdkbuild/java-1.8.0-openjdk-1.8.0.171-1/jre/lib/ext/jaccess.jar
file:/C:/Program%20Files/ojdkbuild/java-1.8.0-openjdk-1.8.0.171-1/jre/lib/ext/localedata.jar
file:/C:/Program%20Files/ojdkbuild/java-1.8.0-openjdk-1.8.0.171-1/jre/lib/ext/nashorn.jar
file:/C:/Program%20Files/ojdkbuild/java-1.8.0-openjdk-1.8.0.171-1/jre/lib/ext/sunec.jar
file:/C:/Program%20Files/ojdkbuild/java-1.8.0-openjdk-1.8.0.171-1/jre/lib/ext/sunjce_provider.jar
file:/C:/Program%20Files/ojdkbuild/java-1.8.0-openjdk-1.8.0.171-1/jre/lib/ext/sunmscapi.jar
file:/C:/Program%20Files/ojdkbuild/java-1.8.0-openjdk-1.8.0.171-1/jre/lib/ext/sunpkcs11.jar
file:/C:/Program%20Files/ojdkbuild/java-1.8.0-openjdk-1.8.0.171-1/jre/lib/ext/zipfs.jar
file:/C:/AeryLocal/Work/ws-aery/test/bin/
file:/C:/Users/nulll/.gradle/caches/modules-2/files-2.1/org.apache.commons/commons-math3/3.6.1/e4ba98f1d4b3c80ec46392f25e094a6a2e58fcbf/commons-math3-3.6.1.jar
file:/C:/Users/nulll/.gradle/caches/modules-2/files-2.1/com.google.guava/guava/23.0/c947004bb13d18182be60077ade044099e4f26f1/guava-23.0.jar
file:/C:/Users/nulll/.gradle/caches/modules-2/files-2.1/com.google.code.findbugs/jsr305/1.3.9/40719ea6961c0cb6afaeb6a921eaa1f6afd4cfdf/jsr305-1.3.9.jar
file:/C:/Users/nulll/.gradle/caches/modules-2/files-2.1/com.google.errorprone/error_prone_annotations/2.0.18/5f65affce1684999e2f4024983835efc3504012e/error_prone_annotations-2.0.18.jar
file:/C:/Users/nulll/.gradle/caches/modules-2/files-2.1/com.google.j2objc/j2objc-annotations/1.1/976d8d30bebc251db406f2bdb3eb01962b5685b3/j2objc-annotations-1.1.jar
file:/C:/Users/nulll/.gradle/caches/modules-2/files-2.1/org.codehaus.mojo/animal-sniffer-annotations/1.14/775b7e22fb10026eed3f86e8dc556dfafe35f2d5/animal-sniffer-annotations-1.14.jar
file:/C:/Users/nulll/.gradle/caches/modules-2/files-2.1/junit/junit/4.12/2973d150c0dc1fefe998f834810d68f278ea58ec/junit-4.12.jar
file:/C:/Users/nulll/.gradle/caches/modules-2/files-2.1/org.hamcrest/hamcrest-core/1.3/42a25dc3219429f0e5d060061f71acb49bf010a0/hamcrest-core-1.3.jar
sun.boot.class.path
(列印系統參數"sun.boot.class.path"預設之搜索路徑, 也就是BootStrapClassLoader的搜索路徑)
C:\Program Files\ojdkbuild\java-1.8.0-openjdk-1.8.0.171-1\jre\lib\resources.jar
C:\Program Files\ojdkbuild\java-1.8.0-openjdk-1.8.0.171-1\jre\lib\rt.jar
C:\Program Files\ojdkbuild\java-1.8.0-openjdk-1.8.0.171-1\jre\lib\sunrsasign.jar
C:\Program Files\ojdkbuild\java-1.8.0-openjdk-1.8.0.171-1\jre\lib\jsse.jar
C:\Program Files\ojdkbuild\java-1.8.0-openjdk-1.8.0.171-1\jre\lib\jce.jar
C:\Program Files\ojdkbuild\java-1.8.0-openjdk-1.8.0.171-1\jre\lib\charsets.jar
C:\Program Files\ojdkbuild\java-1.8.0-openjdk-1.8.0.171-1\jre\lib\jfr.jar
C:\Program Files\ojdkbuild\java-1.8.0-openjdk-1.8.0.171-1\jre\classes
java.ext.dirs
(列印系統參數"java.ext.dirs"預設之搜索路徑,
也就是ExtClassLoader的搜索路徑
)
C:\Program Files\ojdkbuild\java-1.8.0-openjdk-1.8.0.171-1\jre\lib\ext
C:\WINDOWS\Sun\Java\lib\ext
java.class.path
(列印系統參數"java.class.path"預設之搜索路徑
,
也就是AppClassLoader的搜索路徑
)
C:\Program Files\ojdkbuild\java-1.8.0-openjdk-1.8.0.171-1\jre\lib\resources.jar
C:\Program Files\ojdkbuild\java-1.8.0-openjdk-1.8.0.171-1\jre\lib\rt.jar
C:\Program Files\ojdkbuild\java-1.8.0-openjdk-1.8.0.171-1\jre\lib\jsse.jar
C:\Program Files\ojdkbuild\java-1.8.0-openjdk-1.8.0.171-1\jre\lib\jce.jar
C:\Program Files\ojdkbuild\java-1.8.0-openjdk-1.8.0.171-1\jre\lib\charsets.jar
C:\Program Files\ojdkbuild\java-1.8.0-openjdk-1.8.0.171-1\jre\lib\ext\access-bridge-64.jar
C:\Program Files\ojdkbuild\java-1.8.0-openjdk-1.8.0.171-1\jre\lib\ext\cldrdata.jar
C:\Program Files\ojdkbuild\java-1.8.0-openjdk-1.8.0.171-1\jre\lib\ext\dnsns.jar
C:\Program Files\ojdkbuild\java-1.8.0-openjdk-1.8.0.171-1\jre\lib\ext\jaccess.jar
C:\Program Files\ojdkbuild\java-1.8.0-openjdk-1.8.0.171-1\jre\lib\ext\localedata.jar
C:\Program Files\ojdkbuild\java-1.8.0-openjdk-1.8.0.171-1\jre\lib\ext\nashorn.jar
C:\Program Files\ojdkbuild\java-1.8.0-openjdk-1.8.0.171-1\jre\lib\ext\sunec.jar
C:\Program Files\ojdkbuild\java-1.8.0-openjdk-1.8.0.171-1\jre\lib\ext\sunjce_provider.jar
C:\Program Files\ojdkbuild\java-1.8.0-openjdk-1.8.0.171-1\jre\lib\ext\sunmscapi.jar
C:\Program Files\ojdkbuild\java-1.8.0-openjdk-1.8.0.171-1\jre\lib\ext\sunpkcs11.jar
C:\Program Files\ojdkbuild\java-1.8.0-openjdk-1.8.0.171-1\jre\lib\ext\zipfs.jar
C:\AeryLocal\Work\ws-aery\test\bin
C:\Users\nulll\.gradle\caches\modules-2\files-2.1\org.apache.commons\commons-math3\3.6.1\e4ba98f1d4b3c80ec46392f25e094a6a2e58fcbf\commons-math3-3.6.1.jar
C:\Users\nulll\.gradle\caches\modules-2\files-2.1\com.google.guava\guava\23.0\c947004bb13d18182be60077ade044099e4f26f1\guava-23.0.jar
C:\Users\nulll\.gradle\caches\modules-2\files-2.1\com.google.code.findbugs\jsr305\1.3.9\40719ea6961c0cb6afaeb6a921eaa1f6afd4cfdf\jsr305-1.3.9.jar
C:\Users\nulll\.gradle\caches\modules-2\files-2.1\com.google.errorprone\error_prone_annotations\2.0.18\5f65affce1684999e2f4024983835efc3504012e\error_prone_annotations-2.0.18.jar
C:\Users\nulll\.gradle\caches\modules-2\files-2.1\com.google.j2objc\j2objc-annotations\1.1\976d8d30bebc251db406f2bdb3eb01962b5685b3\j2objc-annotations-1.1.jar
C:\Users\nulll\.gradle\caches\modules-2\files-2.1\org.codehaus.mojo\animal-sniffer-annotations\1.14\775b7e22fb10026eed3f86e8dc556dfafe35f2d5\animal-sniffer-annotations-1.14.jar
C:\Users\nulll\.gradle\caches\modules-2\files-2.1\junit\junit\4.12\2973d150c0dc1fefe998f834810d68f278ea58ec\junit-4.12.jar
C:\Users\nulll\.gradle\caches\modules-2\files-2.1\org.hamcrest\hamcrest-core\1.3\42a25dc3219429f0e5d060061f71acb49bf010a0\hamcrest-core-1.3.jar
/** 測試由不同ClassLoder載入相同.class之狀況 */
testDifferentClassLoader();
loadTestClass1.equals(loadTestClass2) = false
(不同ClassLoader載入之.class定義為不同的Class<?>)
Exception in thread "main" java.lang.ClassCastException: org.aery.practice.classloader.TestClass cannot be cast to org.aery.practice.classloader.TestClass
at org.aery.practice.classloader.AeryPractice_ClassLoader.testDifferentClassLoader(AeryPractice_ClassLoader.java:39)
at org.aery.practice.classloader.AeryPractice_ClassLoader.main(AeryPractice_ClassLoader.java:12)
(因此做轉型時會報錯)