1.JVM運行流程
JVM運行流程如下圖所示:
2.JVM基本結構
JVM基本機構包括:類加載器膜钓,執(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對象:保存類的定義或者結構,放入堆中)
初始化:執(zhí)行類的構造器杰标,為類的一些靜態(tài)變量賦予正確的初始值
初始化的代碼事例如下圖所示:
注意:static塊中的變量必須在static塊之前聲明兵怯,否則只能寫,不能讀腔剂。
構造器包含以下內(nèi)容:
a.static變量(不能是final修飾的常量)
b.static塊
構造器與構造方法的區(qū)別:
a.構造器:構造Class對象
b.構造方法:實例化對象媒区,先要執(zhí)行類的構造器,才能實例化對象
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í)行座掘,他與父類加載器不是繼承關系,而是組合關系(成員變量)柔滔。
他加載的任務必須先委托給他的父類加載器溢陪,如果父類沒有加載到這個類,則繼續(xù)由發(fā)起加載的這個類加載器加載(子類需要重寫findClass方法)廊遍,如果沒有加載到嬉愧,則報出ClassNotFoudException
注意:
a.使用雙親委派模型的原因:避免類的重復加載,不會產(chǎn)生不必要的安全隱患
b.findClass的目的就是用來實現(xiàn)自定義ClassLoader的方法(建議重寫findClass喉前,不建議重寫loadClass)
4.ClassLoader源碼解析
ClassLoader主要方法LoadClass没酣,實現(xiàn)了加載類的二進制字節(jié)流的功能
繼續(xù)往下看,
紅圈序號說明如下:
- 先根據(jù)傳入的全限定名稱加載Class對象
- 如果class對象為空的情況卵迂,分別對應以下兩種情況:
- 如果他的父類加載器不為空裕便,則由父類加載器執(zhí)行類的裝載工作
- 如果父類加載器為空,則由Bootstrap ClassLoader執(zhí)行類的裝載工作见咒,此時其實由JVM內(nèi)核執(zhí)行加載動作偿衰,代碼如下所示:
- 如果class對象為空的話,則由子類的findClass實現(xiàn)類的裝載工作
一般情況下改览,實現(xiàn)自定義類加載器則需要重寫findClass方法即可下翎。
5.從源碼分析實現(xiàn)自定義類加載器
測試代碼用idea開發(fā),代碼結構如下:
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 {
//關閉流
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();
}
}
如上代碼,運行結果如下:
以上結果說明: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");
結果如下:
到此稿黄,已得到想要的結果喊衫。