ClassLoader就是類(lèi)加載器。ClassLoader的作用就是將class文件加載到j(luò)vm虛擬機(jī)中去。jvm啟動(dòng)時(shí),并不會(huì)一次性加載所有的class文件,而是按需動(dòng)態(tài)加載。
Class文件
平時(shí)我們?cè)贗DE上編寫(xiě)的都是.java文件,.java文件并不能直接在JVM上運(yùn)行,例子:
public class Main {
//運(yùn)行入口main函數(shù)
public static void main(String[] args) {
System.out.println("Hello world!");
}
}
java文件

編譯文件

生成.class文件

運(yùn)行.class文件

.class文件是字節(jié)碼格式文件,java虛擬機(jī)不能識(shí)別.java源文件,只能識(shí)別運(yùn)行.class文件,因此我們需要用javac將.java轉(zhuǎn)換為.class文件。
JAVA類(lèi)加載流程
三大類(lèi)加載器:
- Bootstrap ClassLoader 最頂層的加載類(lèi),主要加載 核心類(lèi)庫(kù),%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等。
- Extention ClassLoader 擴(kuò)展的類(lèi)加載器,加載 目錄%JRE_HOME%\lib\ext目錄下的jar包和class文件。
- Appclass Loader也稱(chēng)為SystemAppClass 加載 當(dāng)前應(yīng)用的classpath的所有類(lèi)。
PS:Bootstrap ClassLoader可以通過(guò)java -Xbootclasspath/a:path來(lái)修改加載的目錄,而Extention ClassLoader可以通過(guò)-D java.ext.dirs來(lái)修改加載的目錄
入口源碼
查看精簡(jiǎn)源碼,以下是java虛擬機(jī)的入口應(yīng)用
public class Launcher {
private static Launcher launcher = new Launcher();
private static String bootClassPath =
System.getProperty("sun.boot.class.path");
public static Launcher getLauncher() {
return launcher;
}
private ClassLoader loader;
public Launcher() {
// Create the extension class loader
ClassLoader extcl;
try {
extcl = ExtClassLoader.getExtClassLoader();
} catch (IOException e) {
throw new InternalError(
"Could not create extension class loader", e);
}
// Now create the class loader to use to launch the application
try {
loader = AppClassLoader.getAppClassLoader(extcl);
} catch (IOException e) {
throw new InternalError(
"Could not create application class loader", e);
}
//設(shè)置AppClassLoader為線程上下文類(lèi)加載器,這個(gè)文章后面部分講解
Thread.currentThread().setContextClassLoader(loader);
}
/*
* Returns the class loader used to launch the main application.
*/
public ClassLoader getClassLoader() {
return loader;
}
/*
* The class loader used for loading installed extensions.
*/
static class ExtClassLoader extends URLClassLoader {}
/**
* The class loader used for loading from java.class.path.
* runs in a restricted security context.
*/
static class AppClassLoader extends URLClassLoader {}
}
- Launcher初始化了ExtClassLoader和AppClassLoader
- Launcher中并沒(méi)有看見(jiàn)BootstrapClassLoader,但通過(guò)
System.getProperty("sun.boot.class.path")得到了字符串bootClassPath,這個(gè)應(yīng)該就是BootstrapClassLoader加載的jar包路徑。
為了驗(yàn)證剛才說(shuō)的第2點(diǎn),我們運(yùn)行下程序:

果然都是jre下的jar包或class文件
ExtClassLoader源碼
/*
* The class loader used for loading installed extensions.
*/
static class ExtClassLoader extends URLClassLoader {
static {
ClassLoader.registerAsParallelCapable();
}
/**
* create an ExtClassLoader. The ExtClassLoader is created
* within a context that limits which files it can read
*/
public static ExtClassLoader getExtClassLoader() throws IOException
{
final File[] dirs = getExtDirs();
try {
// Prior implementations of this doPrivileged() block supplied
// aa synthesized ACC via a call to the private method
// ExtClassLoader.getContext().
return AccessController.doPrivileged(
new PrivilegedExceptionAction<ExtClassLoader>() {
public ExtClassLoader run() throws IOException {
int len = dirs.length;
for (int i = 0; i < len; i++) {
MetaIndex.registerDirectory(dirs[i]);
}
return new ExtClassLoader(dirs);
}
});
} catch (java.security.PrivilegedActionException e) {
throw (IOException) e.getException();
}
}
private static File[] getExtDirs() {
String s = System.getProperty("java.ext.dirs");
File[] dirs;
if (s != null) {
StringTokenizer st =
new StringTokenizer(s, File.pathSeparator);
int count = st.countTokens();
dirs = new File[count];
for (int i = 0; i < count; i++) {
dirs[i] = new File(st.nextToken());
}
} else {
dirs = new File[0];
}
return dirs;
}
......
}
前面說(shuō)過(guò),ExtClassLoader可以通過(guò)java -Xbootclasspath/a:path來(lái)修改加載的目錄,我們打印下String s = System.getProperty("java.ext.dirs");

果然都是Extensions下的jar包或class文件
AppClassLoader源碼
/**
* The class loader used for loading from java.class.path.
* runs in a restricted security context.
*/
static class AppClassLoader extends URLClassLoader {
public static ClassLoader getAppClassLoader(final ClassLoader extcl)
throws IOException
{
final String s = System.getProperty("java.class.path");
final File[] path = (s == null) ? new File[0] : getClassPath(s);
return AccessController.doPrivileged(
new PrivilegedAction<AppClassLoader>() {
public AppClassLoader run() {
URL[] urls =
(s == null) ? new URL[0] : pathToURLs(path);
return new AppClassLoader(urls, extcl);
}
});
}
......
}
按照前面的規(guī)矩,打印final String s = System.getProperty("java.class.path");

圖上指的路徑,就是項(xiàng)目存放編譯生成的class的路徑
此時(shí):我們已經(jīng)知道BootstrapClassLoader、ExtClassLoader、AppClassLoader實(shí)際就是查詢(xún)相應(yīng)環(huán)境屬性sun.boot.class.path、java.ext.dirs和java.class.path來(lái)加載資源文件的。
我們?cè)谂軅€(gè)例子:

Main.class這個(gè)類(lèi)的類(lèi)加載為AppClassLoader,那!為啥String.class卻報(bào)錯(cuò)了,然道是Stirng.class這個(gè)類(lèi)沒(méi)有類(lèi)加載器加載。答案是否定的,String.class不僅有類(lèi)加載器加載,且是Bootstrap ClassLoader加載的。
每個(gè)類(lèi)加載器都有一個(gè)父加載器
比如Main.class的父加載器是AppClassLoader,那AppClassLoader的父加載器呢?可以使用getParent

很明顯看出AppClassLoader的父加載器就是ExtClassLoader,那ExtClassLoader的父加載器呢?

又是空指針,然道ExtClassLoader沒(méi)有父加載器?
父加載器不是父類(lèi)
static class ExtClassLoader extends URLClassLoader {}
static class AppClassLoader extends URLClassLoader {}
ExtClassLoader和AppClassLoader其實(shí)都是URLClassLoader的子類(lèi),為啥AppClassLoader的getParent()得到的卻是ExtClassLoader實(shí)例呢?我們先來(lái)看看URLClassLoader。

URLClassLoader的源碼中并沒(méi)有找到getParent()方法。這個(gè)方法在ClassLoader.java中。
ClassLoader源碼
public abstract class ClassLoader {
// The parent class loader for delegation
// Note: VM hardcoded the offset of this field, thus all new fields
// must be added *after* it.
private final ClassLoader parent;
// The class loader for the system
// @GuardedBy("ClassLoader.class")
private static ClassLoader scl;
private ClassLoader(Void unused, ClassLoader parent) {
this.parent = parent;
...
}
protected ClassLoader(ClassLoader parent) {
this(checkCreateClassLoader(), parent);
}
protected ClassLoader() {
this(checkCreateClassLoader(), getSystemClassLoader());
}
public final ClassLoader getParent() {
if (parent == null)
return null;
return parent;
}
public static ClassLoader getSystemClassLoader() {
initSystemClassLoader();
if (scl == null) {
return null;
}
return scl;
}
private static synchronized void initSystemClassLoader() {
if (!sclSet) {
if (scl != null)
throw new IllegalStateException("recursive invocation");
sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
if (l != null) {
Throwable oops = null;
//通過(guò)Launcher獲取ClassLoader
scl = l.getClassLoader();
try {
scl = AccessController.doPrivileged(
new SystemClassLoaderAction(scl));
} catch (PrivilegedActionException pae) {
oops = pae.getCause();
if (oops instanceof InvocationTargetException) {
oops = oops.getCause();
}
}
if (oops != null) {
if (oops instanceof Error) {
throw (Error) oops;
} else {
// wrap the exception
throw new Error(oops);
}
}
}
sclSet = true;
}
}
}
其實(shí)getParent()實(shí)際上就是ClassLoader的屬性parent,parent的賦值是在ClassLoader對(duì)象的構(gòu)造方法中,有兩種情況:
- 外部類(lèi)指定了ClassLoader時(shí),則ClassLoader的parent就是指定的ClassLoader
- 外部類(lèi)未指定時(shí),由
getSystemClassLoader()方法生成,結(jié)合前面sun.misc.Laucher源碼的getClassLoader(),返回的是AppCLassLoader。
總結(jié): 一個(gè)ClassLoader創(chuàng)建時(shí)如果沒(méi)有指定parent,那么它的parent默認(rèn)就是AppClassLoader。
我們?cè)诨氐角懊鍸anucher里的關(guān)鍵代碼:
ClassLoader extcl;
extcl = ExtClassLoader.getExtClassLoader();
loader = AppClassLoader.getAppClassLoader(extcl);
前面我已經(jīng)證明了AppClassLoader的parent是ExtClassLoader實(shí)例,而ExtClassLoader并沒(méi)有對(duì)parent賦值。它調(diào)用了父類(lèi)URLClassLoader的構(gòu)造方法并傳遞3個(gè)參數(shù)。
//單參數(shù)構(gòu)造函數(shù)
public ExtClassLoader(File[] dirs) throws IOException {
super(getExtURLs(dirs), null, factory);
}
//三參數(shù)構(gòu)造函數(shù)
public URLClassLoader(URL[] urls, ClassLoader parent,
URLStreamHandlerFactory factory) {
super(parent);
}
這里也就可以證實(shí),ExtClassLoader的parent為null。也能解釋前面為啥ExtClassLoader調(diào)用getParent()時(shí)會(huì)拋錯(cuò)了。
ExtClassLoader既然parent為空,為什么我們還是說(shuō)Boostrap ClassLoader是它的父加載器呢?
Bootstrap ClassLoader是由C++編寫(xiě)的。
Bootstrap ClassLoader是由C/C++編寫(xiě)的,它本身是虛擬機(jī)的一部分,所以它并不是一個(gè)JAVA類(lèi),也就是無(wú)法在java代碼中獲取它的引用,JVM啟動(dòng)時(shí)通過(guò)Bootstrap類(lèi)加載器加載rt.jar等核心jar包中的class文件,之前的int.class,String.class都是由它加載。
JVM初始化sun.misc.Launcher并創(chuàng)建Extension ClassLoader和AppClassLoader實(shí)例。并將ExtClassLoader設(shè)置為AppClassLoader的父加載器。Bootstrap沒(méi)有父加載器,但是它卻可以作為一個(gè)ClassLoader的父加載器。比如ExtClassLoader。這也可以解釋之前通過(guò)ExtClassLoader的getParent方法獲取為Null的現(xiàn)象。
類(lèi)的加載 雙親委托
一個(gè)類(lèi)加載器找class和recource時(shí),是通過(guò)“委托模式”進(jìn)行。它首先判斷這個(gè)class是不是已經(jīng)加載成功,如果有直接返回,如果沒(méi)有就通過(guò)父加載器查找,不斷遞歸,直到Bootstrap ClassLoader,如果Bootstrap找到了,直接返回,如果沒(méi)有一級(jí)級(jí)返回,最后到底自身去查找這些對(duì)象。這種機(jī)制就叫雙親委托。
流程:

藍(lán)色的代表類(lèi)加載器向上委托的方向,如果當(dāng)前的類(lèi)加載器沒(méi)有查詢(xún)到這個(gè)class對(duì)象已經(jīng)加載就請(qǐng)求父加載器(不一定是父類(lèi))進(jìn)行操作,然后以此類(lèi)推。直到Bootstrap ClassLoader。
如果Bootstrap ClassLoader也沒(méi)有加載過(guò)此class實(shí)例,那么它就會(huì)從它指定的路徑中去查找,如果查找成功則返回,如果沒(méi)有查找成功則交給子類(lèi)加載器,也就是ExtClassLoader,這樣類(lèi)似操作直到終點(diǎn),也就是我上圖中的紅色箭頭示例。
- 一個(gè)AppClassLoader查找資源時(shí),先看看緩存是否有,緩存有從緩存中獲取,否則委托給父加載器。
- 遞歸,重復(fù)第1部的操作。
- 如果ExtClassLoader也沒(méi)有加載過(guò),則由Bootstrap ClassLoader出面,它首先查找緩存,如果沒(méi)有找到的話(huà),就去找自己的規(guī)定的路徑下,也就是
sun.mic.boot.class下面的路徑。找到就返回,沒(méi)有找到,讓子加載器自己去找。 - Bootstrap ClassLoader如果沒(méi)有查找成功,則ExtClassLoader自己在
java.ext.dirs路徑中去查找,查找成功就返回,查找不成功,再向下讓子加載器找。 - ExtClassLoader查找不成功,AppClassLoader就自己查找,在
java.class.path路徑下查找。找到就返回。如果沒(méi)有找到就讓子類(lèi)找,如果沒(méi)有子類(lèi)會(huì)怎么樣?拋出各種異常。
上面的序列,詳細(xì)說(shuō)明了雙親委托的加載流程。委托是從下向上,查找過(guò)程卻是自上至下。

從上面兩張圖可以直觀的看出類(lèi)加載的大致過(guò)程。若要了解更細(xì)點(diǎn)我們還得知道幾個(gè)重要方法loadClass()、findLoadedClass()、findClass()、defineClass()
重要方法 loadClass( )
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先,檢測(cè)是否已經(jīng)加載
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
//父加載器不為空則調(diào)用父加載器的loadClass
c = parent.loadClass(name, false);
} else {
//父加載器為空則調(diào)用Bootstrap Classloader
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;
}
}
- 調(diào)用
findLoadedClass(String)去檢測(cè)這個(gè)class是不是已經(jīng)加載過(guò)了 - 調(diào)用父加載器
loadClass()。若父加載器為null,則jvm內(nèi)置的加載器去替代,也就是Bootstrap ClassLoader。這也解釋ExtClassLoader的parent為null,但卻說(shuō)Bootstrap ClassLoader就是它的父加載器 - 如果向上委托父加載器沒(méi)有加載成功,則通過(guò)
findClass(String)查找。
4.如果class在上面的步驟中找到了,參數(shù)resolve又是true的話(huà),那么loadClass()又會(huì)調(diào)用resolveClass(Class)這個(gè)方法來(lái)生成最終的Class對(duì)象
PS:如果要編寫(xiě)一個(gè)classLoader的子類(lèi),也就是自定義一個(gè)classloader,建議覆蓋findClass()方法,而不要直接改寫(xiě)loadClass()方法。
PS:本文
整理自以下博客
一看你就懂,超詳細(xì)java中的ClassLoader詳解
若有發(fā)現(xiàn)問(wèn)題請(qǐng)致郵 caoyanglee92@gmail.com