前言
上一篇文章桅狠,《從頭開始學習JVM(四):類加載器(中)》截型,我們知道了類加載器的基本原理趴荸,但是知道了這些原理之后,我們對類加載器的底層的邏輯宦焦,算不上有多清楚明白发钝。
我們僅僅是意識到,類加載器的種類有多少赶诊,類加載器的加載機制雙親委派模型以及如何打破雙親委派模型等等等相關的一些原理笼平。
但是在這些原理的背后,是什么在支撐著呢舔痪?
想一想吧寓调,所謂的JVM,本身就是一直概念上的產(chǎn)物锄码,并不是物理上的東西夺英,那么作為JVM一部分的類加載器,以及我們的類加載流程滋捶,也只是我們在概念上的劃分痛悯。而支持著這些概念上劃分的,就是我們的代碼重窟。這些代碼里面载萌,有C++代碼,有java代碼,這些代碼扭仁,組合成了一個個閉合的邏輯垮衷,在現(xiàn)實層面,實現(xiàn)了我們所要實現(xiàn)的原理和概念理論乖坠。
今天這篇文章搀突,我們嘗試從代碼層面,來解讀我們的類加載器以及一些相關的類加載機制熊泵。
正文
1.Launcher類
由于BootStapClassLoader加載器是由C++語言寫的仰迁,是嵌入到了JVM內核中,也就是說顽分,從java代碼的層面我們是看不到的徐许,因此我們不去細究這個啟動類加載器。
JVM生成的第一個類是Launcher類怯邪,先看代碼绊寻,如下:
public static void main(String[] args) {
ClassLoader classLoader = Launcher.class.getClassLoader();
System.out.println("加載器:" + classLoader);
}
代碼運行結果為:
E:\jdk\bin\java.exe
加載器:null
Process finished with exit code 0
請注意,最后的根加載器打印出的是null悬秉,這不代表著就是真的null澄步,只是因為啟動類加載器是由C++寫的,所以在java中顯示就是為null和泌。
通過代碼運行的結果為null村缸,我們可以得知,這個類是由BootstrapClassLoader加載器加載的武氓。
也就是說梯皿,JVM在啟動的時候,就生成了BootstrapClassLoader加載器县恕,而BootrapClassLoader加載器會JVM啟動的第一時間就會去創(chuàng)建Launcher類的實例东羹。
那么,為什么要創(chuàng)建Laucher類的實例呢忠烛?我們先來看一下Launcher類的內部代碼属提,如下:
public class Launcher {
private static URLStreamHandlerFactory factory = new Launcher.Factory();
private static Launcher launcher = new Launcher();
private static String bootClassPath = System.getProperty("sun.boot.class.path");
private ClassLoader loader;
private static URLStreamHandler fileHandler;
public static Launcher getLauncher() {
return launcher;
}
public Launcher() {
Launcher.ExtClassLoader var1;
try {
//創(chuàng)建ExtClassLoader加載器
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
//通過創(chuàng)建的ExtClassLoader加載器來獲取AppClassLoader設置為默認加載器加載器。
//并且將AppClassLoader設置為默認加載器
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
Thread.currentThread().setContextClassLoader(this.loader);
String var2 = System.getProperty("java.security.manager");
if (var2 != null) {
SecurityManager var3 = null;
if (!"".equals(var2) && !"default".equals(var2)) {
try {
var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
} catch (IllegalAccessException var5) {
;
} catch (InstantiationException var6) {
;
} catch (ClassNotFoundException var7) {
;
} catch (ClassCastException var8) {
;
}
} else {
var3 = new SecurityManager();
}
if (var3 == null) {
throw new InternalError("Could not create SecurityManager: " + var2);
}
System.setSecurityManager(var3);
}
}
}
通過這段代碼美尸,我們發(fā)現(xiàn)冤议,Launcher類中有一個構造方法Launcher(),這個方法在Launcher創(chuàng)建的時候师坎,就會創(chuàng)建ExtClassLoader加載器恕酸,通過創(chuàng)建的ExtClassLoader加載器來創(chuàng)建AppClassLoader加載器,并且將AppClassLoader設置為默認的系統(tǒng)加載器(也就是當前線程的上下文類加載器)胯陋。
也因此蕊温,當我們想要獲取當前應用程序的AppClassLoader加載器的時候袱箱,可以通過以下代碼獲取:
public static void main(String[] args) {
ClassLoader classLoader = Launcher.getLauncher().getClassLoader();
System.out.println("加載器:" + classLoader);
}
運行結果如下:
E:\jdk\bin\java.exe
加載器:sun.misc.Launcher$AppClassLoader@18b4aac2
Process finished with exit code 0
然后寿弱,既然創(chuàng)建了ExtClassLoader加載器和AppClassLoader加載器犯眠,那么我們可以去看看這兩個加載器的源碼。請注意的是症革,這兩個加載器都是Launcher類的靜態(tài)內部類。
2.ExtclassLoader和AppClassLoader
ExtclassLoader:
static class ExtClassLoader extends URLClassLoader類鸯旁, {
public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
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]);
}
return new Launcher.ExtClassLoader(var0);
}
});
} catch (PrivilegedActionException var2) {
throw (IOException)var2.getException();
}
}
}
ExtclassLoader加載器繼承了URLClassLoader類噪矛,URLClassLoader這個類是繼承了頂級類ClassLoader的,是ClassLoader類的一個擴展铺罢。
ExtclassLoader沒有重寫ClassLoader類的loadClass()方法艇挨,而是直接調用了頂級類ClassLoader的loadClass()方法。
AppClassLoader:
static class AppClassLoader extends URLClassLoader {
final URLClassPath ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this);
public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
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() {
URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2);
return new Launcher.AppClassLoader(var1x, var0);
}
});
}
public Class<?> loadClass(String var1, boolean var2) throws ClassNotFoundException {
int var3 = var1.lastIndexOf(46);
if (var3 != -1) {
SecurityManager var4 = System.getSecurityManager();
if (var4 != null) {
var4.checkPackageAccess(var1.substring(0, var3));
}
}
if (this.ucp.knownToNotExist(var1)) {
Class var5 = this.findLoadedClass(var1);
if (var5 != null) {
if (var2) {
this.resolveClass(var5);
}
return var5;
} else {
throw new ClassNotFoundException(var1);
}
} else {
return super.loadClass(var1, var2);
}
}
}
AppclassLoader加載器也繼承了URLClassLoader類韭赘。
AppclassLoader重寫了ClassLoader類的loadClass()方法缩滨。
3.繼承了ClassLoader的URLClassLoader類
java.net.URLClassLoader類是繼承了ClassLoader的一個類,是ClassLoader類的一個擴展泉瞻。
ClassLoader只能加載classpath下面的類脉漏,而URLClassLoader則可以加載任意路徑下的類。
一般動態(tài)加載類的時候袖牙,都是直接用Class.forName()這個方法侧巨,但這個方法只能創(chuàng)建程序中已經(jīng)引用的類,并且只能用包名的方法進行索引鞭达,比如Java.lang.String司忱,不能對一個.class文件或者一個不在程序引用里的.jar包中的類進行創(chuàng)建。
URLClassLoader提供了這個功能畴蹭,它讓我們可以通過以下幾種方式進行加載:
- 文件: (從文件系統(tǒng)目錄加載)
- jar包: (從Jar包進行加載)
- Http: (從遠程的Http服務進行加載)
4.抽象的類加載器ClassLoader(頂級類)
java.lang.ClassLoader是Java層面對類加載器的抽象坦仍,這個類規(guī)范了類加載的基本流程,是類加載中的頂級類叨襟,其中比較重要的屬性及方法如下:
- parent():父類加載器的引用繁扎,一個類加載器通常都會保存一個父類加載器的引用,用于實現(xiàn)雙親委派機制芹啥。
- loadClass()方法锻离,該方法為類加載器的核心方法,其中實現(xiàn)了雙親委派的邏輯墓怀。
代碼如下:
public abstract class ClassLoader {
//通過這個類汽纠,我們可以找到這個加載器的父類加載器
private final ClassLoader parent;
//name是要加載的類的名稱,resolve是判斷這個類是否要解析
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先傀履,檢查類是否已經(jīng)加載
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
//如果父類加載器不為null(也就是不是啟動類加載器)虱朵,那就調用父類加載器的loadClass()方法來加載這個類
c = parent.loadClass(name, false);
} else {
//如果父類加載器為null莉炉,那么就獲取到啟動類加載器BootstrapClassLoader來加載這個類
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 如果沒有找到類,則拋出ClassNotFoundException異常
// from the non-null parent class loader
}
if (c == null) {
long t1 = System.nanoTime();
//如果仍然沒有找到碴犬,那么就調用這個加載器本身的findClass()去找到這個類
c = findClass(name);
// 這是定義類加載器絮宁,記錄數(shù)據(jù)
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
//如果傳進來的參數(shù)resolve為true,那么我們就去解析這個類
resolveClass(c);
}
//返回這個類對應的Class對象
return c;
}
}
}
要說明一下服协,在這個loadClass()方法中绍昂,我們調用的方法大部分都是native方法,說白了都是使用了C++寫的偿荷,在java里面是看不到具體實現(xiàn)的窘游。
從以上代碼我們可以看出來,是實現(xiàn)了類加載機制之雙親委派模型的跳纳。
5.加載器之間的層級關系
類加載器有著一定的層級關系忍饰,比如說userClassLoader的父類加載器是AppClassLoader,而AppClassLoader的父類加載器是ExtClassLoader寺庄,而ExtClassLoader的父類加載器是BootStrapClassLoader艾蓝,而BootStrapClassLoader就是最高層級的類加載器了。
我先寫出如下一段代碼:
public static void main(String[] args) throws IOException {
//獲取應用程序類加載器AppClassLoader
ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
System.out.println("應用程序類加載器:" + appClassLoader);
Enumeration<URL> enums = appClassLoader.getResources("");
while (enums.hasMoreElements()) {
System.out.println("應用程序類加載器加載路徑:"+enums.nextElement());
break;
}
//獲取應用程序類加載器的父類加載器(也就是擴展類加載器ExtClassLoader)
ClassLoader extClassLoader = appClassLoader.getParent();
System.out.println("擴展類加載器:" + extClassLoader);
System.out.println("擴展類加載器加載路徑:" + System.getProperty("java.ext.dirs"));
//獲取擴展類加載器的父類加載器(也就是啟動類加載器BootStrapClassLoader)
ClassLoader bootClassLoader = extClassLoader.getParent();
System.out.println("根類加載器:" + bootClassLoader);
}
這段代碼斗塘,運行的結果如下:
E:\jdk\bin\java.exe
應用程序類加載器 :sun.misc.Launcher$AppClassLoader@18b4aac2
擴展類加載器 :sun.misc.Launcher$ExtClassLoader@156643d4
根類加載器 :null
Process finished with exit code 0
所以我們可以看到赢织,程序最后運行的結果,是和我們代碼里面所需要的結果逛拱,是一致的敌厘。也就是說,確實如我之前所說朽合,總結如下:
- userClassLoader的父類加載器是AppClassLoader俱两。
- AppClassLoader的父類加載器是ExtClassLoader。
- ExtClassLoader的父類加載器是BootStrapClassLoader曹步。
- BootStrapClassLoader是最高層級的類加載器宪彩。
- AppClassLoader是程序默認的類加載器。
6.類加載器的加載路徑
在上面那段代碼的基礎上讲婚,我們稍微修改一下代碼:
public static void main(String[] args){
//獲取到啟動類加載器BootStrapClassLoader
URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
for(URL url : urls) {
System.out.println("BootstrapClassLoader的加載路徑: "+url);
break;
}
//獲取到擴展類加載器ExtClassLoader
URLClassLoader extClassLoader = (URLClassLoader)ClassLoader.getSystemClassLoader().getParent();
urls = extClassLoader.getURLs();
for(URL url : urls){
System.out.println("ExtClassLoader的加載路徑: "+url);
break;
}
//取得應用(系統(tǒng))類加載器AppClassLoader
ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
Enumeration<URL> enums = appClassLoader.getResources("");
while (enums.hasMoreElements()) {
System.out.println("AppClassLoader的加載路徑:"+enums.nextElement());
break;
}
}
得出的代碼的運行結果如下:
E:\jdk\bin\java.exe
BootstrapClassLoader負責加載存放在 <JAVA_HOME>\lib目錄
2. 的加載路徑: file:/E:/jdk/jre/lib/resources.jar
ExtClassLoader的加載路徑: file:/E:/jdk/jre/lib/ext/access-bridge-64.jar
AppClassLoader的加載路徑: file:/L:/order-service/order-service-web/target/classes/
Process finished with exit code 0
因此尿孔,從最后的結果來看,我們可以獲取到這些類加載器的加載路徑筹麸,總結如下:
- BootstrapClassLoader負責加載存放在 <JAVA_HOME>\lib目錄
- ExtClassLoader主要加載JAVA中的一些拓展類活合,java.ext.dirs目錄中加載類庫,或者從JDK安裝目錄:jre/lib/ext目錄下加載類庫,是啟動類加載器的子類物赶。
- AppClassLoader負責加載環(huán)境變量classpath或者系統(tǒng)屬性java.class.path指定路徑下的類庫白指。
7.手寫一個UserClassLoader加載器
我們知道,實現(xiàn)屬于自己的類加載器UserClassLoader有兩種方式:
- 繼承java.lang.ClassLoader類酵紫,重寫findClass()方法
- 如果沒有太復雜的需求告嘲,可以直接繼承URLClassLoader類错维,重寫loadClass方法,具體可參考AppClassLoader和ExtClassLoader(我在上面已經(jīng)截圖說明了橄唬,請注意赋焕,這種方案會打破雙親委派模型)。
第二種方案仰楚,直接模擬AppClassLoader或者ExtClassLoader的源碼就可以了隆判,我們今天缸血,就來通過第一種方案,來手寫一個我們的UserClassLoader加載器捎泻。
7.1 創(chuàng)建一個加載器MyUserClassLoader
package com.blog.permission.classLoad;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
public class MyUserClassLoader extends ClassLoader{
private String rootPath;
public MyUserClassLoader() {
super();
}
public MyUserClassLoader(String rootPath) {
this.rootPath = rootPath;
}
/**
* 用于尋找類文件
* @param className 類文件的全限定名
* @return
* @throws ClassNotFoundException
*/
@Override
protected Class<?> findClass(String className) {
Class<?> clz = findLoadedClass(className);
if (clz != null)
return clz;
byte[] classData = loadClassData(className);
clz = defineClass(className, classData, 0, classData.length);
return clz;
}
/**
* 這是將文件轉換為二進制字節(jié)碼
* @param className
* @return
*/
private byte[] loadClassData(String className) {
String pathName = rootPath + className.replace(".", "/") + ".class";
System.out.println(pathName);
byte[] bytes = null;
try (FileInputStream fis = new FileInputStream(pathName);
ByteArrayOutputStream bos = new ByteArrayOutputStream();) {
byte[] flush = new byte[1024 * 1024];
int len = -1;
while (-1 != (len = fis.read(flush))) {
bos.write(flush);
}
bytes = bos.toByteArray();
} catch (Exception e) {
System.out.println("異常");
}
return bytes;
}
}
7.2 創(chuàng)建一個要加載的java類:Shuaige.java
package com.blog.permission.classLoad;
public class Shuaige {
public static void main(String[] args) {
System.out.println("I'm so cool,you underStand?");
}
}
7.3 將shuaige.java文件編譯成Shuaige.class文件
7.4 創(chuàng)建一個運行的類
package com.blog.permission.classLoad;
public class TestClassLoad {
public static void main(String[] args) throws ClassNotFoundException {
MyUserClassLoader fileSystemClassLoader = new MyUserClassLoader("/com/blog/permission/classLoad");
Class<?> c = fileSystemClassLoader.loadClass("com.blog.permission.classLoad.Shuaige");
System.out.println(c);
}
}
最后形成的層級目錄如下所示:
7.5 運行結果如下
E:\jdk\bin\java.exe
class com.blog.permission.classLoad.Shuaige
Process finished with exit code 0
總結
通過對加載器的代碼的分析和解讀笆豁,很明顯闯狱,我們對類加載的機制有了更加深刻的了解。
當然抛计,某種程度上來講,本篇文章的源碼解析吹截,也只是解析了一部分而已,還有很多地方是沒有涉及到的波俄。但是透過這些源碼晨逝,我們對類加載器,有了質感上的提升懦铺,類加載器以及類加載過程捉貌,在我們心里,不再是虛擬的不落到實處的概念冬念,而是真真切切存在著的由代碼實現(xiàn)了的東西了趁窃。
到此,類加載器的整個解讀就已經(jīng)結束了急前。
參考博客
https://blog.csdn.net/how_interesting/article/details/80091472
https://blog.csdn.net/chuodan5158/article/details/100765519
https://www.cnblogs.com/chinaifae/p/10401523.html
https://blog.csdn.net/weixin_39161031/article/details/83000750
https://www.cnblogs.com/rogge7/p/7766522.html
參考書籍
周志明《深入理解java虛擬機》