·# 類加載機制深度決議
個人的理解為大家分享從JDK原始碼級別徹底剖析JVM類加載機,如有錯誤十分抱歉,還請指出博主會立馬改正,一、類加載運行全程序
當我們用java命令運行某個類的main函式啟動程式時,首先需要通過類加載器把主類加載到 JVM(這里以Math類為例),
package com.tuling.jvm;
/**
* @author Kang
* @version 1.0
*/
public class Math {
public static final int initData = 666;
public static User user = new User () ;
public int compute() { //一個 方法對應一塊堆疊幀記憶體區域
int a = 1;
int b = 2;
int c=(a+b)*10;
return c;
}
public static void main(String[] args) {
Math math = new Math() ;
math.compute();
System.out.println("test");
}
}
通過Java命令執行代碼的大體流程如下(涉及類加載器下面做介紹):

我們加載Math類是要經過loadClass的類加載程序有如下幾步:

- 加載 >> 驗證 >> 準備 >> 決議 >> 初始化 >> 使用 >> 卸載
- 加載:在硬碟上查找并通過IO讀入位元組碼檔案,使用到類時才會加載,例如呼叫類的main()方法,new物件等等,在加載階段會在記憶體中生成一個代表這個類的java.lang.Class物件,作為方法區這個類的各種資料的訪問入口
- 驗證:校驗位元組碼檔案的正確性
- 準備:給類的靜態變數分配記憶體,并賦予默認值
決議:將符號參考替換為直接參考,該階段會把一些靜態方法(符號參考,比如main()方法)替換為指向資料所存記憶體的指標或句柄等(直接參考),這是所謂的靜態鏈接程序(類加載期間完成),動態鏈接是在程式運行期間完成的將符號參考替換為直接參考, - 初始化:對類的靜態變數初始化為指定的值,執行靜態代碼塊
二、類加載器和雙親委派機制
1.上面的類加載程序主要是通過類加載器來實作的,Java里有如下幾種類加載器
- 引導類加載器:負責加載支撐JVM運行的位于JRE的lib目錄下的核心類別庫,比如 rt.jar、charsets.jar等
- 擴展類加載器:負責加載支撐JVM運行的位于JRE的lib目錄下的ext擴展目錄中的JAR 類包
- 應用程式類加載器:負責加載ClassPath路徑下的類包,主要就是加載你自己寫的那 些類
- 自定義加載器:負責加載用戶自定義路徑下的類包
JVM類加載器是有親子層級結構的,如下圖

這里類加載其實就有一個雙親委派機制,加載某個類時會先委托父加載器尋找目標類,找不到再 委托上層父加載器加載,如果所有父加載器在自己的加載類路徑下都找不到目標類,則在自己的 類加載路徑中查找并載入目標類,
比如我們的Math類,最先會找應用程式類加載器加載,應用程式類加載器會先委托擴展類加載 器加載,擴展類加載器再委托引導類加載器,頂層引導類加載器在自己的類加載路徑里找了半天 沒找到Math類,則向下退回加載Math類的請求,擴展類加載器收到回復就自己加載,在自己的 類加載路徑里找了半天也沒找到Math類,又向下退回Math類的加載請求給應用程式類加載器, 應用程式類加載器于是在自己的類加載路徑里找Math類,結果找到了就自己加載了,,
雙親委派機制說簡單點就是,先找父親加載,不行再由兒子自己加載
2.類加載器初始化程序?
參見類運行加載全程序圖可知其中會創建JVM啟動器實體sun.misc.Launcher,可以用編程工具全域搜索這個Launcher類
在Launcher構造方法內部,其創建了兩個類加載器,分別是 sun.misc.Launcher.ExtClassLoader(擴展類加載器)和,sun.misc.Launcher.AppClassLoader(應 用類加載器), JVM默認使用Launcher的getClassLoader()方法回傳的類加載器AppClassLoader的實體加載我們 的應用程式,
構造方法如下:
//Launcher的構造方法
public Launcher() {
Launcher.ExtClassLoader var1;
try {
//構造擴展類加載器,在構造的程序中將其父加載器設定為null
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
//構造應用類加載器,在構造的程序中將其父加載器設定為ExtClassLoader,
//Launcher的loader屬性值是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);
}
}
我們來看下應用程式類加載器AppClassLoader加載類的雙親委派機制原始碼,AppClassLoader 的loadClass方法最侄訓呼叫其父類ClassLoader的loadClass方法,該方法的大體邏輯如下:
- 首先,檢查一下指定名稱的類是否已經加載過,如果加載過了,就不需要再加載,直接 回傳,
- 如果此類沒有加載過,那么,再判斷一下是否有父加載器;如果有父加載器,則由父加 載器加載(即呼叫parent.loadClass(name, false);).或者是呼叫bootstrap類加載器來加 載,
- 如果父加載器及bootstrap類加載器都沒有找到指定的類,那么呼叫當前類加載器的 findClass方法來完成類加載,
//ClassLoader的loadClass方法,里面實作了雙親委派機制
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 檢查當前類加載器是否已經加載了該類
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) { //如果當前加載器父加載器不為空則委托父加載器加載該類
c = parent.loadClass(name, false);
} else { //如果當前加載器父加載器為空則委托引導類加載器加載該類
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();
//都會呼叫URLClassLoader的findClass方法在加載器的類路徑里查找并加載該類
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;
}
}
3.為什么要設計雙親委派機制?
- 沙箱安全機制:自己寫的java.lang.String.class類不會被加載,這樣便可以防止核心 API庫被隨意篡改
- 避免類的重復加載:當父親已經加載了該類時,就沒有必要子ClassLoader再加載一 次,保證被加載類的唯一性
4.全盤負責委托機制
全盤負責”是指當一個ClassLoder裝載一個類時,除非顯示的使用另外一個ClassLoder,該類 所依賴及參考的類也由這個ClassLoder載入
總結
因為是第一次寫博客,如有諸多不好,還望包容,轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/177353.html
標籤:其他
