1.JVM運行流程
JVM運行流程如下圖所示:

2.JVM基本結(jié)構(gòu)
JVM基本機構(gòu)包括:類加載器,執(zhí)行引擎,運行時數(shù)據(jù)區(qū),本地接口
Class Files-->ClassLoader-->運行時數(shù)據(jù)區(qū)-->執(zhí)行引擎(本地庫接口)-->本地方法庫
具體參考我之前的文章:jvm內(nèi)存模型概述
3.ClassLoader雙親委派模型
由上面的概念可知,ClassLoader的作用就是將字節(jié)碼文件(Class Files)加載到運行時數(shù)據(jù)區(qū)。
3.1類的裝載過程
加載-->連接(驗證、準備、解析)-->初始化-->使用-->卸載
說明:在此只是簡單的介紹一下類的裝載過程,詳細的過程請自行百度。
加載:取得類的字節(jié)碼文件的二進制字節(jié)流,加載之后會產(chǎn)生一個類對象(Class對象:保存類的定義或者結(jié)構(gòu),放入堆中)
初始化:執(zhí)行類的構(gòu)造器,為類的一些靜態(tài)變量賦予正確的初始值
初始化的代碼事例如下圖所示:

注意:static塊中的變量必須在static塊之前聲明,否則只能寫,不能讀。
構(gòu)造器包含以下內(nèi)容:
a.static變量(不能是final修飾的常量)
b.static塊
構(gòu)造器與構(gòu)造方法的區(qū)別:
a.構(gòu)造器:構(gòu)造Class對象
b.構(gòu)造方法:實例化對象,先要執(zhí)行類的構(gòu)造器,才能實例化對象
3.2 ClassLoader簡介
JDK已有的ClassLoader:
Bootstrap ClassLoader(啟動加載器,必須要首先啟動JVM,通過C++實現(xiàn),存在于JVM的內(nèi)核中,用null表示):主要加載jdk lib下的rt.jar等
Extension ClassLoader (Java實現(xiàn),繼承自ClassLoader):加載%JAVA_HOME%/lib/ext/*.jar
App ClassLoader(Java實現(xiàn),繼承自ClassLoader):加載classpath下的jar
自定義類加載器,也必須繼承自ClassLoader:加載自定義路徑下的jar
3.3 雙親委派模型
類加載器都有一個父類加載器先執(zhí)行,他與父類加載器不是繼承關(guān)系,而是組合關(guān)系(成員變量)。
他加載的任務(wù)必須先委托給他的父類加載器,如果父類沒有加載到這個類,則繼續(xù)由發(fā)起加載的這個類加載器加載(子類需要重寫findClass方法),如果沒有加載到,則報出ClassNotFoudException
注意:
a.使用雙親委派模型的原因:避免類的重復(fù)加載,不會產(chǎn)生不必要的安全隱患
b.findClass的目的就是用來實現(xiàn)自定義ClassLoader的方法(建議重寫findClass,不建議重寫loadClass)
4.ClassLoader源碼解析
ClassLoader主要方法LoadClass,實現(xiàn)了加載類的二進制字節(jié)流的功能

繼續(xù)往下看,

紅圈序號說明如下:
- 先根據(jù)傳入的全限定名稱加載Class對象
- 如果class對象為空的情況,分別對應(yīng)以下兩種情況:
- 如果他的父類加載器不為空,則由父類加載器執(zhí)行類的裝載工作
- 如果父類加載器為空,則由Bootstrap ClassLoader執(zhí)行類的裝載工作,此時其實由JVM內(nèi)核執(zhí)行加載動作,代碼如下所示:

- 如果class對象為空的話,則由子類的findClass實現(xiàn)類的裝載工作


一般情況下,實現(xiàn)自定義類加載器則需要重寫findClass方法即可。
5.從源碼分析實現(xiàn)自定義類加載器
測試代碼用idea開發(fā),代碼結(jié)構(gòu)如下:

a. 自定義ClassLoader類如下:
package lyx.demo.classloader;
import java.io.*;
/**
* Created by landyChris on 2017/10/29.
*/
public class MyClassLoader extends ClassLoader {
private String path;//加載類的路徑
private String name;//類加載器的名稱
//指定父類加載器
public MyClassLoader(ClassLoader parent, String path, String name) {
super(parent);//顯示指定父類加載器
this.path = path;
this.name = name;
}
public MyClassLoader(String path, String name) {
super();//讓系統(tǒng)類加載器成為該類的父加載器
this.path = path;
this.name = name;
}
/**
* 通過自定義ClassLoader加載我們自己定義的類
* @param name 全路徑名稱,類似:lyx.demo.xxx.Demo
* @return
* @throws ClassNotFoundException
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] data = readClassFile2ByteArray(name);
return this.defineClass(name,data,0,data.length);
}
/**
* 獲取.class字節(jié)數(shù)組
* lyx.demo.xxx.Demo-->d:/lyx/demo/xxx/Demo.class
* @param name
* @return
*/
private byte[] readClassFile2ByteArray(String name) {
InputStream is = null;
byte returnData[] = null;
name = name.replaceAll("\\.", File.separator);
String filePath = this.path + name + ".class";
File file = new File(filePath);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
is = new FileInputStream(file);
int tmp = 0;
while ((tmp = is.read()) != -1){
baos.write(tmp);
}
returnData = baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
} finally {
//關(guān)閉流
try {
if(is != null) {
is.close();
}
if(baos != null) {
baos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return returnData;
}
@Override
public String toString() {
return this.name;
}
}
b. 新建一個Test類,用于測試用途,代碼如下:
public class Test {
public Test() {
System.out.println("A ClassLoader:" + this.getClass().getClassLoader() + " from classpath");
}
}
然后在自己的某個盤符下新建一個目錄,比如我電腦是d:/tmp

還是上面的Test類,只是把打印信息改為以下語句:
System.out.println("A ClassLoader:" + this.getClass().getClassLoader() + " from customer ,path=d:/tmp");

在d:tmp目錄下編譯好后,如下圖所示:

c.編寫測試類
package lyx.demo.classloader;
/**
* Created by landyChris on 2017/10/29.
*/
public class TestClassLoader {
public static void main(String[] args) throws Exception{
MyClassLoader loader = new MyClassLoader(null,"d:/tmp/","landy");
Class<?> c = loader.loadClass("Test");
c.newInstance();
}
}
如上代碼,運行結(jié)果如下:

以上結(jié)果說明:ClassLoader的執(zhí)行是符合雙親委派模型的,它會先讓父類加載器(App ClassLoader)執(zhí)行l(wèi)oadClass的動作,如果找到Test類則直接返回,此例中,在該classpath下是有Test類的,如下圖所示:

為了達到我們需要執(zhí)行的是d:/tmp目錄下的Test類,我們可以兩種做法,一種是直接刪除idea工程中的Test類。一種是利用:啟動加載器可以用null值表示,我們可以修改以下代碼達到目的:
MyClassLoader loader = new MyClassLoader(null,"d:/tmp/","landy");
結(jié)果如下:

到此,已得到想要的結(jié)果。