概念
Java類加載器(Java Classloader)是Java運行時環境(Java Runtime Environment)的一部分,負責動態加載Java類到Java虛擬機的內存空間中,用於加載系統、網絡或者其他來源的類文件。Java源代碼通過javac編譯器編譯成類文件,然后JVM來執行類文件中的字節碼來執行程序。
類文件編譯流程圖
我們以下圖為例子,比如我們創建一個ClassLoaderTest.java文件運行,經過javac編譯,然后生成ClassLoaderTest.class文件。這個java文件和生成的class文件都是存儲在我們的磁盤當中。但如果我們需要將磁盤中的class文件在java虛擬機內存中運行,需要經過一系列的類的生命周期(加載、連接(驗證-->准備-->解析)和初始化操作,最后就是我們的java虛擬機內存使用自身方法區中字節碼二進制數據去引用堆區的Class對象。
通過這個流程圖,我們就很清楚地了解到類的加載就是由java類加載器實現的,作用將類文件進行動態加載到java虛擬機內存中運行。
建立TestHelloWorld.java
編譯java文件為class文件 -> javac TestHelloWorld.java
生成編譯好的TestHelloWorld.class文件
使用java 查看編譯好的class文件內容
javap -c -p -l TestHelloWorld.class
JVM在執行TestHelloWorld
之前會先解析class二進制內容,JVM執行的其實就是如上javap
命令生成的字節碼。
類加載器分類
引導類加載器(BootstrapClassLoader)
引導類加載器(BootstrapClassLoader),底層原生代碼是C++語言編寫,屬於jvm一部分,不繼承java.lang.ClassLoader類,也沒有父加載器,主要負責加載核心java庫(即JVM本身),存儲在/jre/lib/rt.jar目錄當中。(同時處於安全考慮,BootstrapClassLoader只加載包名為java、javax、sun等開頭的類)。
擴展類加載器(ExtensionsClassLoader)
擴展類加載器(ExtensionsClassLoader),由sun.misc.Launcher$ExtClassLoader類實現,用來在/jre/lib/ext或者java.ext.dirs中指明的目錄加載java的擴展庫。Java虛擬機會提供一個擴展庫目錄,此加載器在目錄里面查找並加載java類。
App類加載器/系統類加載器(AppClassLoader)
App類加載器/系統類加載器(AppClassLoader),由sun.misc.Launcher$AppClassLoader實現,一般通過通過(java.class.path或者Classpath環境變量)來加載Java類,也就是我們常說的classpath路徑。通常我們是使用這個加載類來加載Java應用類,可以使用ClassLoader.getSystemClassLoader()來獲取它。
自定義類加載器(UserDefineClassLoader)
自定義類加載器(UserDefineClassLoader),除了上述java自帶提供的類加載器,我們還可以通過繼承java.lang.ClassLoader類的方式實現自己的類加載器。
雙親委派機制
雙親委派機制的概念
通常情況下,我們就可以使用JVM默認三種類加載器進行相互配合使用,且是按需加載方式,就是我們需要使用該類的時候,才會將生成的class文件加載到內存當中生成class對象進行使用,且加載過程使用的是雙親委派模式,及把需要加載的類交由父加載器進行處理。
如上圖類加載器層次關系,我們可以將其稱為類加載器的雙親委派模型。但注意的是,他們之間並不是"繼承"體系,而是委派體系。當上述特定的類加載器接到加載類的請求時,首先會先將任務委托給父類加載器,接着請求父類加載這個類,當父類加載器無法加載時(其目錄搜素范圍沒有找到所需要的類時),子類加載器才會進行加載使用。這樣可以避免有些類被重復加載。
雙親委派機制的好處
1、這樣就是能夠實現有些類避免重復加載使用,直接先給父加載器加載,不用子加載器再次重復加載。
2、保證java核心庫的類型安全。比如網絡上傳輸了一個java.lang.Object類,通過雙親模式傳遞到啟動類當中,然后發現其Object類早已被加載過,所以就不會加載這個網絡傳輸過來的java.lang.Object類,保證我們的java核心API庫不被篡改,出現類似用戶自定義java.lang.Object類的情況。
核心方法
loadClass:加載指定的java類
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);
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);
// 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;
}
在loadClass方法中,它先使用了findLoadedClass(String)方法來檢查這個類是否被加載過。
接着使用父加載器調用loadClass(String)方法,如果父加載器為null,類加載器加載jvm內置的加載器。
之后就調用findClass(String) 方法裝載類。
最后通過上述步驟我們找到了對應的類,並且接收到的resolve參數的值為true,那么就會調用resolveClass(Class)方法來處理類。
findCLass:查找指定的Java類
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
findLoadedClass:查找JVM已經加載過的類
protected final Class<?> findLoadedClass(String name) {
if (!checkName(name))
return null;
return findLoadedClass0(name);
}
defineClass:定義一個Java類,將字節碼解析成虛擬機識別的Class對象。往往和findClass()方法配合使用
protected final Class<?> defineClass(byte[] b, int off, int len)
throws ClassFormatError
{
return defineClass(null, b, off, len, null);
}
resolveClass:鏈接指定Java類
protected final void resolveClass(Class<?> c) {
resolveClass0(c);
}
private native void resolveClass0(Class c);
ClassLoader類加載流程
理解Java類加載機制並非易事,這里我們以一個Java的HelloWorld來學習ClassLoader
。
ClassLoader
加載com.tyut.TestHelloWorld
類重要流程如下:
ClassLoader
會調用public Class<?> loadClass(String name)
方法加載com.tyut.TestHelloWorld
類。- 調用
findLoadedClass
方法檢查TestHelloWorld
類是否已經初始化,如果JVM已初始化過該類則直接返回類對象。 - 如果創建當前
ClassLoader
時傳入了父類加載器(new ClassLoader(父類加載器)
)就使用父類加載器加載TestHelloWorld
類,否則使用JVM的Bootstrap ClassLoader
加載。 - 如果上一步無法加載
TestHelloWorld
類,那么調用自身的findClass
方法嘗試加載TestHelloWorld
類。 - 如果當前的
ClassLoader
沒有重寫了findClass
方法,那么直接返回類加載失敗異常。如果當前類重寫了findClass
方法並通過傳入的com.tyut.TestHelloWorld
類名找到了對應的類字節碼,那么應該調用defineClass
方法去JVM中注冊該類。 - 如果調用loadClass的時候傳入的
resolve
參數為true,那么還需要調用resolveClass
方法鏈接類,默認為false。 - 返回一個被JVM加載后的
java.lang.Class
類對象。
自定義類加載過程
定義被加載的類 TestHelloWorld
需要被加載的類 TestHelloWorld.java
//TestHelloWorld.java
package com.tyut;
public class TestHelloWorld {
public String hello() {
return "Hello World~";
}
}
自定義加載器 TestClassLoader.java
使用自定義類加載器重寫findClass
方法,然后在調用defineClass
方法的時候傳入TestHelloWorld
類的字節碼的方式來向JVM中定義一個TestHelloWorld
類,最后通過反射機制就可以調用TestHelloWorld
類的hello
方法了
//TestClassLoader.java
package com.tyut;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class TestClassLoader extends ClassLoader {
// TestHelloWorld類名
private static String testClassName = "com.tyut.TestHelloWorld";
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
// 只處理TestHelloWorld類
if (name.equals(testClassName)) {
byte[] classData = getClassData("com.tyut.TestHelloWorld");
// 調用JVM的native方法向JVM定義TestHelloWorld類
return defineClass(testClassName, classData, 0, classData.length);
}
return super.findClass(name);
}
public static byte[] getClassData(String testClassName) {
InputStream is = null;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
is = new FileInputStream(testClassName);
int temp = -1;
while ((temp = is.read()) != -1) {
baos.write(temp);
}
return baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
if (is != null) {
try {
is.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if (baos != null) {
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
使用加載器調用類
創建一個主函數,使用自定義加載器調用我們的類
package com.tyut;
import java.lang.reflect.Method;
public class testmain {
public static void main(String[] args) throws Exception {
try {
TestClassLoader clsload = new TestClassLoader();
// 使用自定義的類加載器加載TestHelloWorld類
Class testClass = clsload.loadClass("com.tyut.TestHelloWorld");
// 反射創建TestHelloWorld類,等價於 TestHelloWorld t = new TestHelloWorld();
Object testInstance = testClass.newInstance();
// 反射獲取hello方法
Method method = testInstance.getClass().getMethod("hello");
// 反射調用hello方法,等價於 String str = t.hello();
String str = (String) method.invoke(testInstance);
System.out.println(str);
} catch (Exception e) {
e.printStackTrace();
}
}
}
個人理解:
類加載器就是去加載一個我們需要的類,在雙親委派機制都無法加載的情況下,進行本地加載,重新定義 findClass,在方法里面定義我們所需要的內容,最后使用defineClass向JVM定義,我們就可以使JVM執行我們需要的方法了。