前言
以下所述,基於java8所寫;類加載器通過完全限定名(比如,com.mypak.module1.MyClass
)來加載類,它使類可以動態加載類到jvm中,java並未規定類的位置,可以來自本地文件系統,也可以來自網絡。
加載器分類
類加載器分爲根加載器(bootstrap classloader)、擴展類加載器(ext classloader)、系統類加載器(system classloader)、自定義類加載器(通常繼承java.net.URLClassLoader
,重寫findClass()
),它們的關係通常如下。
從圖中可以看出,每個類加載器都有一個父加載器(加載器有引用指向父加載器,而不是繼承),在加載類時,首先check本身是否已加載,如果已加載,則返回,如果未加載,如果有父加載器則交給父加載器,如果沒有父加載器則使用根加載器,如果仍沒找到,則本加載器加載類,每一級加載器都執行相同的操作,這種機制稱爲委託機制,英語是parents delegation model
,翻譯過來是雙親委派機制(其實有點詞不達意)。
- 由於雙親委派機制,加載
java.lang.String
時會一直往上委派,直到根加載器,而根加載器只會加載java_home/jre/lib/rt.jar
中的java.lang.String
,從而確保自定義的java.lang.String
不會加載到jvm中,而不會讓jvm錯亂。 - 類加載器+類的完全限定名,組成了在jvm中的唯一標識,如果類加載器不一樣,即使類限定名相同,也不相等。
類加載器用抽象類java.lang.ClassLoader
表示,通過loadClass
源碼可以看出,它是按照雙親加載的機制來執行的。
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name); //首先,check自身加載器是否已加載目標類
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);//有父加載器,則委託給父加載器
} else {
c = findBootstrapClassOrNull(name); //沒有父加載器,則委託給根加載器
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name); //調用findClass()方法
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
根加載器
根加載器主要是用來加載java_home/jre/lib
下的jar包,比如rt.jar
(含有全部java api的類),根加載器用C/C++實現,用null表示,在java代碼中無法獲取到根加載器。
rt.jar如下
擴展類加載器
用來加載System.getproperty("java.ext.dirs")
也就是java_home/jre/lib/ext`下的jar包,擴展類加載器的父加載器是根加載器。
系統類加載器
用來加載System.getproperty("java.class.path")
也就是我們常說的classpath下的類,此路徑下都是應用程序的類,所以也可稱爲應用程序類加載器,它的父加載器是擴展類加載器,classLoader.getSystemClassLoader()
返回的就是系統類加載器
自定義類加載器
在程序運行時,如需自定義類加載器,通常繼承java.net.URLClassLoader
,重寫findClass
方法,這樣符合雙親委派機制。
實例
系統類加載器–>擴展類加載器–>根加載器
public class MyClassLoader {
public static void main(String[] args) {
// 系統類加載器(應用類加載器)
System.out.println(MyClassLoader.class.getClassLoader());
//擴展類加載器
System.out.println(MyClassLoader.class.getClassLoader().getParent());
//系統類加載器
System.out.println(MyClassLoader.class.getClassLoader().getParent().getSystemClassLoader());
//根加載器
System.out.println(MyClassLoader.class.getClassLoader().getParent().getParent());
//擴展類加載器,java_home/jre/lib/ext/dnsns.jar
System.out.println(DNSNameService.class.getClassLoader());
//根加載器
System.out.println(String.class.getClassLoader());
}
}
輸出
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@1b6d3586
sun.misc.Launcher$AppClassLoader@18b4aac2
null
sun.misc.Launcher$ExtClassLoader@1b6d3586
null
查看源碼得知,ExtClassLoader
加載System.getProperty("java.ext.dirs")
路徑下的類,在我的電腦上是D:\jdk1.8.0_211\jre\lib\ext;
static class ExtClassLoader
extends URLClassLoader
{
private static File[] getExtDirs()
{
String str = System.getProperty("java.ext.dirs");
//省略
}
}
AppClassLoader源碼得知,它加載System.getProperty("java.class.path")
下的類,在我的系統上是D:\jdk1.8.0_211\jre\lib\ext;
、D:\jdk1.8.0_211\jre\lib\;
還有應用的類,可以看到它包含了ExtClassLoader
的加載路徑。
static class AppClassLoader
extends URLClassLoader
{
public static ClassLoader getAppClassLoader(final ClassLoader paramClassLoader)
throws IOException
{
String str = System.getProperty("java.class.path");
//省略
}
}
類加載器+類全限定名在jvm中是類的唯一標識
public class MyClassLoader extends ClassLoader{
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String fileName = name.substring(name.lastIndexOf(".")+1) + ".class";
InputStream is = getClass().getResourceAsStream(fileName);
assert is != null;
byte[] b;
try {
b = new byte[is.available()];
is.read(b);
return defineClass(name,b,0,b.length);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public static void main(String[] args) {
try {
Class<?> aClass = new MyClassLoader().findClass("com.jun.javase.MyClassLoader");
System.out.println(aClass+"---"+MyClassLoader.class);
System.out.println(aClass.getClassLoader()+"---"+MyClassLoader.class.getClassLoader());
System.out.println(MyClassLoader.class == aClass);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
輸出
class com.jun.javase.MyClassLoader---class com.jun.javase.MyClassLoader
[email protected]$AppClassLoader@18b4aac2
false
輸出false
,因爲類加載器不同,即使同一個類,也不相等。