1 類加載器
Java 的類加載,就是把字節(jié)碼格式“.class”文件加載到 JVM的方法區(qū),并在 JVM 的堆區(qū)建立一個(gè)java.lang.Class對(duì)象的實(shí)例,用來封裝Java類相關(guān)的數(shù)據(jù)和方法。 Class對(duì)象可以把它理解成業(yè)務(wù)類的模板,JVM根據(jù)這個(gè)模板來創(chuàng)建具體業(yè)務(wù)類對(duì)象實(shí)例。
JVM類加載使用懶加載的機(jī)制,也就是說并不是在啟動(dòng)時(shí)就把所有的“.class”文件都加載一遍,而是程序在運(yùn)行過程中用到了這個(gè)類才去加載。JVM 類加載是由類加載器來完成的,JDK類加載器的抽象基類為ClassLoader。
在 Java 虛擬機(jī)中,類的唯一性是由類加載器實(shí)例以及類的全名一同確定的。即便是同一串字節(jié)流,經(jīng)由不同的類加載器加載,也會(huì)得到兩個(gè)不同的類。在大型應(yīng)用中,我們往往借助這一特性,來運(yùn)行同一個(gè)類的不同版本。
案例
@Test
public void test() throws Exception {
/** 獲取當(dāng)前Class對(duì)象的類加載器加載指定類,返回Class對(duì)象**/
Class<?> aClass = ClassLoaderTest.class.getClassLoader().loadClass("jvm.Bootstrap");
/** 通過Class對(duì)象獲取構(gòu)造函數(shù) **/
Constructor<?> constructor = aClass.getConstructor();
/** 通過反射構(gòu)造實(shí)例化類對(duì)象**/
Bootstrap Bootstrap = (Bootstrap)constructor.newInstance();
System.out.println(Bootstrap);
}
2 ClassLoader類結(jié)構(gòu)

- ClassLoader:ClassLoader類加載器的基類,其內(nèi)部存在一個(gè)類型為ClassLoader屬性parent,用來實(shí)現(xiàn)類加載器之間的父子關(guān)系。
public abstract class ClassLoader {
...
//父類加載器
private final ClassLoader parent;
- URLClassLoader:URLClassLoader是ClassLoader子類,其內(nèi)部存一個(gè)類型為URLClassPath屬性為ucp,ucp負(fù)責(zé)管理被當(dāng)前類加載器加載Class文件資源路徑。
public class URLClassLoader extends SecureClassLoader implements Closeable {
private final URLClassPath ucp;
3 ClassLoader中核心方法
protected final Class<?> defineClass(String name, byte[] b, int off, int len) throws ClassFormatError{ … }
defineClass是個(gè)工具方法,它的職責(zé)是調(diào)用 native 方法把 Java 類的字節(jié)碼解析成一個(gè) Class 對(duì)象,所謂的 native 方法就是由 C 語言實(shí)現(xiàn)的方法,Java 通過 JNI 機(jī)制調(diào)用。
protected Class<?> findClass(String name) throws ClassNotFoundException { … }
findClass 方法的主要職責(zé)就是找到“.class”文件,.class”文件可能來自文件系統(tǒng)或者網(wǎng)絡(luò),找到后把“.class”文件讀到內(nèi)存中得到字節(jié)碼數(shù)組,然后調(diào)用 defineClass 方法得到 Class 對(duì)象。
public synchronized Class<?> loadClass(String name) throws ClassNotFoundException{ … }
loadClass 是個(gè) public 方法,說明它才是對(duì)外提供服務(wù)的接口,具體實(shí)現(xiàn)也比較清晰:首先檢查這個(gè)類是不是已經(jīng)被加載過了,如果加載過了直接返回,否則交給父加載器去加載。請(qǐng)你注意,這是一個(gè)遞歸調(diào)用,也就是說子加載器持有父加載器的引用,當(dāng)一個(gè)類加載器需要加載一個(gè) Java 類時(shí),會(huì)先委托父加載器去加載,然后父加載器在自己的加載路徑中搜索 Java 類,當(dāng)父加載器在自己的加載范圍內(nèi)找不到時(shí),才會(huì)交還給子加載器加載,這就是雙親委托機(jī)制。
4 Java 虛擬機(jī)中類加載器
在JVM中定義了4類加載器分別為:啟動(dòng)(Bootstrap)類加載器,擴(kuò)展(Extension)類加載器,系統(tǒng)(System)類加載器,以及用戶自定義加載器

4.1 啟動(dòng)(Bootstrap)類加載器
引導(dǎo)類加載器是負(fù)責(zé)加載并管理<JAVA_HOME>/lib 的核心類庫 -Xbootclasspath選項(xiàng)指定的jar包c(diǎn)lass文件對(duì)應(yīng)的Class對(duì)象。
啟動(dòng)(Bootstrap)類加載器是擴(kuò)展(Extension)類加載器的父加載器,是最高等級(jí)的加載器,由于啟動(dòng)類加載器涉及到虛擬機(jī)本地實(shí)現(xiàn)細(xì)節(jié),開發(fā)者無法直接獲取到啟動(dòng)類加載器的引用,所以 不允許直接通過引用進(jìn)行操作。
//從系統(tǒng)屬性中獲取啟動(dòng)(Bootstrap)類加載器加載并管理核心類庫
System.getProperty("sun.boot.class.path")
C:\Program Files\Java\jdk1.8.0_91\jre\lib\resources.jar;
C:\Program Files\Java\jdk1.8.0_91\jre\lib\rt.jar;
C:\Program Files\Java\jdk1.8.0_91\jre\lib\sunrsasign.jar;
C:\Program Files\Java\jdk1.8.0_91\jre\lib\jsse.jar;
C:\Program Files\Java\jdk1.8.0_91\jre\lib\jce.jar;
C:\Program Files\Java\jdk1.8.0_91\jre\lib\charsets.jar;
C:\Program Files\Java\jdk1.8.0_91\jre\lib\jfr.jar;
C:\Program Files\Java\jdk1.8.0_91\jre\classes
4.2 擴(kuò)展(Extension)類加載器
擴(kuò)展類加載器是由Sun的ExtClassLoader(sun.misc.Launcher$ExtClassLoader)實(shí)現(xiàn)的,它負(fù)責(zé)加載并管理 <JAVA_HOME >/lib/ext或者由系統(tǒng)變量-Djava.ext.dir指定位置中class文件對(duì)應(yīng)的Class對(duì)象。
//從系統(tǒng)屬性中獲取擴(kuò)展(Extension)類加載器加載并管理核心類庫
System.getProperty("java.ext.dirs")
C:\Program Files\Java\jdk1.8.0_91\jre\lib\ext;
C:\windows\Sun\Java\lib\ext
4.3 系統(tǒng)(System)類加載器
系統(tǒng)類加載器是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)實(shí)現(xiàn)的,它負(fù)責(zé)加載并管理用戶類路徑(java -classpath或-Djava.class.path變量所指定的URL資源)中class文件對(duì)應(yīng)的Class對(duì)象。開發(fā)者可以直接使用系統(tǒng)類加載器。
//從系統(tǒng)屬性中獲取系統(tǒng)(System)類加載器加載并管理核心類庫
System.getProperty("java.class.path")
...省略
D:\project_alibaba\jvm-in-action\target\classes;
...省略
4.4 案例
package jvm;
import java.lang.reflect.Constructor;
/**獲取JVM類加載器 **/
public class ClassLoaderTest {
public static void main(String[] args) {
try {
System.out.println(ClassLoader.getSystemClassLoader());
System.out.println(ClassLoader.getSystemClassLoader().getParent());
System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent());
} catch (Exception e) {
e.printStackTrace();
}
}
}
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@45ee12a7
null
這些類加載器的工作原理是一樣的,區(qū)別是它們的加載路徑不同,也就是說 findClass 這個(gè)方法查找的路徑不同。
5 類加載(雙親委派機(jī)制)
首先檢查這個(gè)類是不是已經(jīng)被加載過了,如果加載過了直接返回,否則交給父加載器去加載。請(qǐng)你注意,這是一個(gè)遞歸調(diào)用,也就是說子加載器持有父加載器的引用,當(dāng)一個(gè)類加載器需要加載一個(gè) Java 類時(shí),會(huì)先委托父加載器去加載,然后父加載器在自己的加載路徑中搜索 Java 類,當(dāng)父加載器在自己的加載范圍內(nèi)找不到時(shí),才會(huì)交還給子加載器加載,直到最終交給不存在父類加載器的啟動(dòng)(Bootstrap)類加載器加載。

5.1 實(shí)現(xiàn)流程
雙親委派模型對(duì)于保證Java程序的穩(wěn)定運(yùn)作很重要,它的具體實(shí)現(xiàn)在java.lang.ClassLoader類loadClass()方法中。
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
protected synchronized Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
// 首先判斷該類是否已經(jīng)被加載
Class c = findLoadedClass(name);
if (c == null) {
//如果沒有被加載,就委托給父類加載
try {
if (parent != null) {
//如果存在父類加載器,就委派給父類加載器加載,這里是一個(gè)遞歸調(diào)用的過程。
c = parent.loadClass(name, false);
} else { // 遞歸終止條件
// 由于啟動(dòng)類加載器無法被Java程序直接引用,因此默認(rèn)用 null 替代
// parent == null就意味著由啟動(dòng)類加載器嘗試加載該類,
// 即通過調(diào)用 native方法 findBootstrapClass0(String name)加載
c = findBootstrapClass0(name);
}
} catch (ClassNotFoundException e) {
// 如果父類加載器不能完成加載請(qǐng)求時(shí),再調(diào)用自身的findClass方法進(jìn)行類加載,若加載成功,findClass方法返回的是defineClass方法的返回值
// 注意,若自身也加載不了,也會(huì)產(chǎn)生ClassNotFoundException異常并向上拋出
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
//沒錯(cuò)!此方法沒有具體實(shí)現(xiàn),只是拋了一個(gè)異常,而且訪問權(quán)限是protected。這充分證明了:這個(gè)方法就是給開發(fā)者重寫用的,即自定義類加載器時(shí)需實(shí)現(xiàn)此方法!
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
- AppClassLoader,ExtClassLoader 對(duì)findClass實(shí)現(xiàn)都繼承自URLClassLoader,
protected Class<?> findClass(final String name)
throws ClassNotFoundException
{
final Class<?> result;
try {
result = AccessController.doPrivileged(
new PrivilegedExceptionAction<Class<?>>() {
public Class<?> run() throws ClassNotFoundException {
//將類的包路徑轉(zhuǎn)換為url資源路徑
String path = name.replace('.', '/').concat(".class");
//通過URLClassLoader URLClassPath ucp中獲取url路徑對(duì)應(yīng)的二進(jìn)制數(shù)據(jù)
Resource res = ucp.getResource(path, false);
if (res != null) {
try {
//讀取二進(jìn)制獲取調(diào)用defineClass獲取Class對(duì)象象
return defineClass(name, res);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
} else {
return null;
}
}
}, acc);
} catch (java.security.PrivilegedActionException pae) {
throw (ClassNotFoundException) pae.getException();
}
if (result == null) {
throw new ClassNotFoundException(name);
}
return result;
}
5.2 案例
ClassLoaderTest,EventID,Sdp屬于被不同類加載器管理的類對(duì)象,使用相同系統(tǒng)類加載器加載時(shí),由于采用雙親委派機(jī)制,獲取Class對(duì)象是由不同的類加載器加載。
@Test
public void classLoaderLoadClass2() throws IOException {
try {
//調(diào)用加載當(dāng)前類的類加載器(這里即為系統(tǒng)類加載器)加載ClassLoaderTest
Class typeLoaded = ClassLoaderTest.class.getClassLoader().loadClass("com.wuhao.jvm.classLoader.ClassLoaderTest");
//查看被加載的ClassLoaderTest對(duì)象是被那個(gè)類加載器加載的
System.out.println(typeLoaded.getClassLoader());
//調(diào)用加載當(dāng)前類的類加載器(這里即為系統(tǒng)類加載器)加載EventID
Class typeLoaded1 = ClassLoaderTest.class.getClassLoader().loadClass("com.sun.java.accessibility.util.EventID");
//查看被加載的ClassLoaderTest對(duì)象是被那個(gè)類加載器加載的
System.out.println(typeLoaded1.getClassLoader());
//調(diào)用加載當(dāng)前類的類加載器(這里即為系統(tǒng)類加載器)加載Sdp
Class typeLoaded2 = ClassLoaderTest.class.getClassLoader().loadClass("com.oracle.net.Sdp");
//查看被加載的ClassLoaderTest對(duì)象是被那個(gè)類加載器加載的
System.out.println(typeLoaded2.getClassLoader());
} catch (Exception e) {
}
}
5.3 模式優(yōu)點(diǎn)
雙親委派模型很好的解決了各個(gè)類加載器的基礎(chǔ)類的統(tǒng)一問題(越基礎(chǔ)的類由越上層的加載器進(jìn)行加載).
例如類java.lang.Object,它存在在rt.jar中,無論哪一個(gè)類加載器要加載這個(gè)類,最終都是委派給處于模型最頂端的啟動(dòng)類加載器進(jìn)行加載,因此Object類在程序的各種類加載器環(huán)境中都是同一個(gè)類。
相反,如果沒有雙親委派模型而是由各個(gè)類加載器自行加載的話,用戶編寫了一個(gè)java.lang.Object的類,并放在程序的ClassPath中,那系統(tǒng)中將會(huì)出現(xiàn)多個(gè)不同的Object類,程序?qū)⒒靵y。因此,如果開發(fā)者嘗試編寫一個(gè)與rt.jar類庫中已有類重名的Java類,將會(huì)發(fā)現(xiàn)可以正常編譯,但是永遠(yuǎn)無法被加載運(yùn)行。
5.4 模式缺點(diǎn)
由于雙親委派模型存在,一個(gè)類被誰加載是由類加載器管理資源所決定,即使某個(gè)類存在于多個(gè)類加載器管理資源中。也只會(huì)被更上程的類加載器加載。這樣就會(huì)導(dǎo)致一個(gè)在被上層類加載中Class中獲取的上程類加載器,是無法加載下層類加載器中管理Class對(duì)象。
5.5 線程上下文類加載器
為了解決這個(gè)問題JVM引用了線程上下文類加載器
這個(gè)類加載器可以通過java.lang.Thread類的setContextClassLoader方法進(jìn)行設(shè)置。如果創(chuàng)建線程時(shí)還未設(shè)置,它將會(huì)從父線程中繼承一個(gè),如果在應(yīng)用程序的全局范圍內(nèi)都沒有設(shè)置過多的話,那這個(gè)類加載器默認(rèn)即使用系統(tǒng)程序類加載器。
5.6 如何破壞雙親委派模型
雙親委派模型并不是一個(gè)強(qiáng)制性的約束模型,而是Java設(shè)計(jì)者推薦給開發(fā)者的類加載器的實(shí)現(xiàn)方式。大多數(shù)的類加載器都遵循這個(gè)模型,雙親委派模型的具體邏輯實(shí)現(xiàn)在ClassLoader的loadClass方法,如果我們自定義類加載器,采用雙親委派模型:只需要重寫ClassLoader的findClass()方法即可,破壞雙親委派模型:重寫ClassLoader的整個(gè)loadClass()方法(因?yàn)殡p親委派模型的邏輯主要實(shí)現(xiàn)就在此方法中,若我們重寫即可破壞掉。)
6 Java虛擬機(jī)對(duì)類加器初始化
當(dāng)運(yùn)行一個(gè)Java程序流程:
1.根據(jù)JVM內(nèi)存配置要求,為JVM申請(qǐng)?zhí)囟ù笮〉膬?nèi)存空間;
2.創(chuàng)建一個(gè)引導(dǎo)類加載器實(shí)例,初步加載系統(tǒng)類到內(nèi)存方法區(qū)區(qū)域中;
3.創(chuàng)建JVM 啟動(dòng)器實(shí)例 Launcher,并取得構(gòu)造擴(kuò)展(Extension)類加載器,系統(tǒng)(System)類加載器;
4.使用系統(tǒng)(System)類加載器加載我們定義的 org.luanlouis.jvm.load.Main類;
5.加載完成時(shí)候JVM會(huì)執(zhí)行Main類的main方法入口,執(zhí)行Main類的main方法;
6.結(jié)束,java程序運(yùn)行結(jié)束,JVM銷毀。
6.1 初始化Launcher
public class Launcher {
public Launcher() {
//創(chuàng)建擴(kuò)展(Extension)類加載器
Launcher.ExtClassLoader var1;
try {
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
//創(chuàng)建系統(tǒng)(System)類加載器
try {
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
//設(shè)置AppClassLoader為線程上下文類加載器,這個(gè)文章后面部分講解
Thread.currentThread().setContextClassLoader(this.loader);
public ClassLoader getClassLoader() {
return loader;
}
static class ExtClassLoader extends URLClassLoader {}
static class AppClassLoader extends URLClassLoader {}
6.2 ExtClassLoader實(shí)例化
1 讀取System.getProperty("java.ext.dirs")參數(shù)對(duì)應(yīng)擴(kuò)展類加載器需要加載jar文件路徑轉(zhuǎn)換為 File[] 文件數(shù)組。
2 調(diào)用public ExtClassLoader(File[] var1) 實(shí)例化 ExtClassLoader 擴(kuò)展類加載器。
3 實(shí)例化ExtClassLoader前實(shí)例化父類URLClassLoader,實(shí)例化URLClassLoader需要將File[]轉(zhuǎn)換為URL[]作為第一個(gè)參數(shù)傳入,并傳入null來表示父類加載器
為啟動(dòng)(Bootstrap)類加載器。
static class ExtClassLoader extends URLClassLoader {
public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
//1 讀取擴(kuò)展類加載器需要加載的文件列表
final File[] var0 = getExtDirs();
try {
return (Launcher.ExtClassLoader)AccessController.doPrivileged(new PrivilegedExceptionAction<Launcher.ExtClassLoader>() {
public Launcher.ExtClassLoader run() throws IOException {
int var1 = var0.length;
for(int var2 = 0; var2 < var1; ++var2) {
MetaIndex.registerDirectory(var0[var2]);
}
//2 實(shí)例化ExtClassLoader
return new Launcher.ExtClassLoader(var0);
}
});
} catch (PrivilegedActionException var2) {
throw (IOException)var2.getException();
}
}
void addExtURL(URL var1) {
super.addURL(var1);
}
//3 構(gòu)造擴(kuò)展類加載器
public ExtClassLoader(File[] var1) throws IOException {
//3 實(shí)例化ExtClassLoader前實(shí)例化父類URLClassLoader
//第一個(gè)參數(shù)將File[]轉(zhuǎn)換為URL[]
//第二個(gè)參數(shù)用來填寫父類加載器,這里父類加載器為啟動(dòng)加載器,用null表示
super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
}
private static File[] getExtDirs() {
String var0 = System.getProperty("java.ext.dirs");
File[] var1;
if (var0 != null) {
StringTokenizer var2 = new StringTokenizer(var0, File.pathSeparator);
int var3 = var2.countTokens();
var1 = new File[var3];
for(int var4 = 0; var4 < var3; ++var4) {
var1[var4] = new File(var2.nextToken());
}
} else {
var1 = new File[0];
}
return var1;
}
private static URL[] getExtURLs(File[] var0) throws IOException {
Vector var1 = new Vector();
for(int var2 = 0; var2 < var0.length; ++var2) {
String[] var3 = var0[var2].list();
if (var3 != null) {
for(int var4 = 0; var4 < var3.length; ++var4) {
if (!var3[var4].equals("meta-index")) {
File var5 = new File(var0[var2], var3[var4]);
var1.add(Launcher.getFileURL(var5));
}
}
}
}
URL[] var6 = new URL[var1.size()];
var1.copyInto(var6);
return var6;
}
...省略代碼
6.3 AppClassLoader實(shí)例化
1 讀取System.getProperty("java.class.path")參數(shù)對(duì)應(yīng)擴(kuò)展類加載器需要加載jar文件路徑轉(zhuǎn)換為 File[] 文件數(shù)組
2 將File[] var1轉(zhuǎn)換為URL[] 構(gòu)建AppClassLoader
3 構(gòu)建父類URLClassLoader 傳入父加載器var2為擴(kuò)展類加載器
static class AppClassLoader extends URLClassLoader {
final URLClassPath ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this);
public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
1 讀取System.getProperty("java.class.path")參數(shù)對(duì)應(yīng)擴(kuò)展類加載器需要加載jar文件路徑轉(zhuǎn)換為 File[] 文件數(shù)組
final String var1 = System.getProperty("java.class.path");
final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1);
return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<Launcher.AppClassLoader>() {
public Launcher.AppClassLoader run() {
//將File[] var1轉(zhuǎn)換為URL[] 構(gòu)建AppClassLoader
URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2);
//構(gòu)建AppClassLoader
return new Launcher.AppClassLoader(var1x, var0);
}
});
}
AppClassLoader(URL[] var1, ClassLoader var2) {
//構(gòu)建父類URLClassLoader 傳入父加載器var2為擴(kuò)展類加載器
super(var1, var2, Launcher.factory);
this.ucp.initLookupCache(this);
}
6.4 URLClassLoader 初始化
- 1 將URL資源交給URLClassPath類型的字段管理
public URLClassLoader(URL[] urls, ClassLoader parent,
URLStreamHandlerFactory factory) {
super(parent);
// this is to make the stack depth consistent with 1.1
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkCreateClassLoader();
}
1 將URL資源交給URLClassPath類型的字段管理
ucp = new URLClassPath(urls, factory);
acc = AccessController.getContext();
}
6.5 ClassLoader 初始化
- 1 設(shè)置父類加載器給屬性parent
private final ClassLoader parent;
this(checkCreateClassLoader(), getSystemClassLoader());
}
protected ClassLoader(ClassLoader parent) {
this(checkCreateClassLoader(), parent);
}
private ClassLoader(Void unused, ClassLoader parent) {
//1 設(shè)置父類加載器給屬性parent
this.parent = parent;
if (ParallelLoaders.isRegistered(this.getClass())) {
parallelLockMap = new ConcurrentHashMap<>();
package2certs = new ConcurrentHashMap<>();
domains =
Collections.synchronizedSet(new HashSet<ProtectionDomain>());
assertionLock = new Object();
} else {
// no finer-grained lock; lock on the classloader instance
parallelLockMap = null;
package2certs = new Hashtable<>();
domains = new HashSet<>();
assertionLock = this;
}
}