柚子快報(bào)激活碼778899分享:開發(fā)語言 JVM類加載器
類加載過程
類加載過程主要為 加載->連接->初始化。連接過程分為 驗(yàn)證->準(zhǔn)備->解析。
加載是類加載過程的第一步,主要完成下面3件事情
通過全類名獲取定義此類的二進(jìn)制字節(jié)流將字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)(.class文件中靜態(tài)存儲(chǔ)的字節(jié)碼格式,包含了類的元數(shù)據(jù)和具體的字節(jié)碼指令)轉(zhuǎn)換為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)在內(nèi)存中生成一個(gè)代表該類的Class對(duì)象,作為方法區(qū)這些數(shù)據(jù)的訪問入口。
類加載器
類加載器介紹
類加載器是一個(gè)負(fù)責(zé)加載類的對(duì)象,ClassLoader是一個(gè)抽象類。給定類的二進(jìn)制名稱,類加載器嘗試定位或生成構(gòu)成類定義的數(shù)據(jù)。典型的策略是將名稱轉(zhuǎn)換為文件名,然后從文件系統(tǒng)中讀取該名稱的“類文件”。 每個(gè)Java類都有一個(gè)引用指向加載他的ClassLoader,不過,數(shù)組類不是通過ClassLoader創(chuàng)建的,而是JVM在需要的時(shí)候自動(dòng)創(chuàng)建的,數(shù)組類通過getClassLoader()方法獲取ClassLoader的時(shí)候和該數(shù)組的元素類型的ClassLoader是一致的。
總結(jié):
類加載器是一個(gè)負(fù)責(zé)加載類的對(duì)象,用于實(shí)現(xiàn)類加載過程中的加載這一步。每個(gè)Java類都有一個(gè)引用指向加載他的ClassLoader。數(shù)組類不是通過ClassLoader創(chuàng)建的,是由JVM直接生成的。
class Class
...
private final ClassLoader classLoader;
@CallerSensitive
public ClassLoader getClassLoader() {
//...
}
...
}
簡(jiǎn)單來說,類加載器的主要作用就是加載Java類的字節(jié)碼(.class文件)到JVM中(在內(nèi)存中生成一個(gè)代表該類的Class對(duì)象)。
類加載器加載規(guī)則
JVM啟動(dòng)的時(shí)候,并不會(huì)一次性加載所有的類,而是根據(jù)需要去動(dòng)態(tài)加載。也就是說,大部分類在具體用到的時(shí)候才會(huì)去加載,這樣對(duì)內(nèi)存更加友好。 對(duì)于已經(jīng)加載的類會(huì)被放在ClassLoader中。在類加載的時(shí)候,系統(tǒng)會(huì)首先判斷當(dāng)前類是否被加載過,已經(jīng)被加載的類會(huì)直接返回,否則才會(huì)嘗試加載。也就是說,對(duì)于一個(gè)類加載器來說,相同二進(jìn)制名稱的類只會(huì)被加載一次。
類加載器總結(jié)
JVM中內(nèi)置了三個(gè)重要的ClassLoader:
BootStrapClassLoader(啟動(dòng)類加載器):最頂層的加載類,由C++實(shí)現(xiàn),通常表示為null,并且沒有父級(jí)。主要用來加載JDK內(nèi)部的核心類庫(kù)(%JAVA_HOME%/lib目錄下的 rt.jar、resources.jar、charsets.jar等 jar 包和類)以及被 -Xbootclasspath參數(shù)指定的路徑下的所有類。ExtensionClassLoader(擴(kuò)展類加載器):主要負(fù)責(zé)加載 %JRE_HOME%/lib/ext 目錄下的 jar 包和類以及被 java.ext.dirs 系統(tǒng)變量所指定的路徑下的所有類。AppClassLoader(應(yīng)用程序類加載器):面向我們用戶的加載器,負(fù)責(zé)加載當(dāng)前應(yīng)用classpath下的所有jar包和類。 除此之外,用戶還可以加入自定義的類加載器來進(jìn)行擴(kuò)展,以滿足自己的特殊需求,如:我們可以對(duì)Java類的字節(jié)碼(.class文件)進(jìn)行加密,加載時(shí)利用自定義類加載器對(duì)其解密。
除了BootstrapClassLoader是JVM自身的一部分之外,其他所有類加載器都是在JVM外部實(shí)現(xiàn)的,并且全都繼承自ClassLoader抽象類。這樣做的好處是用戶可以自定義類加載器,以便讓應(yīng)用程序自己決定如何去獲取所需要的類。
自定義類加載器
ClassLoader類有兩個(gè)關(guān)鍵的方法:
protected Class loadClass(String name, boolean resolve): 加載指定的二進(jìn)制名稱的類,實(shí)現(xiàn)了雙親委派機(jī)制。name為類的二進(jìn)制名稱,resolve如果為true,在加載時(shí)調(diào)用resolveClass(Class> c) 方法解析該類。protected Class findClass(String name):根據(jù)類的二進(jìn)制名稱來查找類,默認(rèn)實(shí)現(xiàn)是空方法。
如果我們不想打破雙親委派模型,就重寫ClassLoader類中的findClass()方法即可,無法被父類加載器加載的類最終會(huì)通過這個(gè)方法被加載。但是,如果想打破雙親委派模型則需要重寫loadClass()方法。
雙親委派模型
雙親委派模型是Java類加載機(jī)制的一種設(shè)計(jì)模式,用于解決類加載過程中類加載順序和安全問題,他定義了類加載器間的協(xié)作關(guān)系,確保Java核心庫(kù)的類優(yōu)先被加載,同時(shí)避免類的重復(fù)加載。
基本概念
類加載器:JVM中負(fù)責(zé)加載類文件的組件,每個(gè)類加載器都有父類加載器(除了BootStrapClassLoader)雙親委派
當(dāng)一個(gè)類加載器加載類時(shí),首先將加載請(qǐng)求委派給父類加載器,父類加載器繼續(xù)向上委派,直到請(qǐng)求達(dá)到頂層的跟加載器。如果父類加載器無法完成加載請(qǐng)求,則子類加載器才會(huì)嘗試自己加載類。
工作流程
加載類請(qǐng)求:當(dāng)一個(gè)類加載器接收到類加載請(qǐng)求時(shí)(如ClassLoader.loadClass(String name)),他不會(huì)立即嘗試加載該類,而是首先將請(qǐng)求委派給父類加載器。委派過程:父類加載器重復(fù)這個(gè)過程,繼續(xù)請(qǐng)求向上委派,直到到達(dá)頂層的根加載器。類加載:根加載器嘗試加載類,如果成功,返回類的應(yīng)用,失敗,則拋出ClassNotFoundException。如果根加載器無法加載類,控制權(quán)返回給子類加載器,子類加載器再嘗試自己加載類。 代碼解析
protected Class> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
//首先,檢查該類是否已經(jīng)加載過
Class c = findLoadedClass(name);
if (c == null) {
//如果 c 為 null,則說明該類沒有被加載過
long t0 = System.nanoTime();
try {
if (parent != null) {
//當(dāng)父類的加載器不為空,則通過父類的loadClass來加載該類
c = parent.loadClass(name, false);
} else {
//當(dāng)父類的加載器為空,則調(diào)用啟動(dòng)類加載器來加載該類
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
//非空父類的類加載器無法找到相應(yīng)的類,則拋出異常
}
if (c == null) {
//當(dāng)父類加載器無法加載時(shí),則調(diào)用findClass方法來加載該類
//用戶可通過覆寫該方法,來自定義類加載器
long t1 = System.nanoTime();
c = findClass(name);
//用于統(tǒng)計(jì)類加載器相關(guān)的信息
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
//對(duì)類進(jìn)行l(wèi)ink操作
resolveClass(c);
}
return c;
}
}
每當(dāng)一個(gè)類加載器接收到加載請(qǐng)求時(shí),會(huì)先將請(qǐng)求轉(zhuǎn)發(fā)給父類加載器,在父類加載器沒有找到所請(qǐng)求的類的情況下,該類加載器才會(huì)嘗試去加載。
結(jié)合上述分析,再梳理一次雙親委派流程
在類加載的時(shí)候,系統(tǒng)會(huì)首先判斷當(dāng)前類是否被加載過,已經(jīng)被加載的類會(huì)直接返回,否則才會(huì)嘗試加載(每個(gè)父類加載器都會(huì)走一遍這個(gè)流程)。類加載器在進(jìn)行類加載的時(shí)候,他首先不會(huì)自己去嘗試加載這個(gè)類,而是把請(qǐng)求委派給父類加載器來完成(調(diào)用父類加載器loadClass()方法來加載類),這樣的話,所有請(qǐng)求最終都會(huì)傳送到頂層的啟動(dòng)類加載器。只有當(dāng)父類加載器返回自己無法完成這個(gè)加載請(qǐng)求,子加載器才會(huì)嘗試自己去加載(調(diào)用自己的finaClass()方法來加載類)。如果子類加載器也無法加載這個(gè)類,那么他會(huì)拋出一個(gè)ClassNotFoundException 異常。
雙親委派模型的好處
雙親委派保證了Java程序的穩(wěn)定運(yùn)行,可以避免類的重復(fù)加載(JVM區(qū)分不同類的方法不僅僅根據(jù)類名,相同的類文件被不同的類加載器加載產(chǎn)生的是兩個(gè)不同的類)。保證了Java的核心API不被篡改。 如果沒有使用雙親委派模型,假如我們編寫了一個(gè)成為java.lang.Object類的話,那么程序運(yùn)行的時(shí)候,系統(tǒng)就會(huì)出現(xiàn)兩個(gè)不同的Object類。雙親委派模型可以保證加載的是 JRE 里的那個(gè) Object 類,而不是你寫的 Object 類。這是因?yàn)?AppClassLoader 在加載你的 Object 類時(shí),會(huì)委托給 ExtClassLoader 去加載,而 ExtClassLoader 又會(huì)委托給 BootstrapClassLoader,BootstrapClassLoader 發(fā)現(xiàn)自己已經(jīng)加載過了 Object 類,會(huì)直接返回,不會(huì)去加載你寫的 Object 類。
打破雙親委派模型
盡管雙親委派模型在 Java 類加載機(jī)制中提供了安全性和一致性,但在某些特殊情況下,開發(fā)者可能需要打破這個(gè)模型。例如,當(dāng)需要在某個(gè)類加載器中加載特定版本的類而不依賴于父類加載器時(shí),可以采取以下幾種方法:
1. 自定義類加載器
public class CustomClassLoader extends ClassLoader {
@Override
public Class> loadClass(String name) throws ClassNotFoundException {
if (name.startsWith("com.example")) {
// 自定義加載指定包下的類
return findClass(name);
}
// 否則,使用默認(rèn)的雙親委派機(jī)制
return super.loadClass(name);
}
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
try {
String fileName = name.replace('.', '/') + ".class";
InputStream is = getClass().getClassLoader().getResourceAsStream(fileName);
byte[] bytes = new byte[is.available()];
is.read(bytes);
return defineClass(name, bytes, 0, bytes.length);
} catch (Exception e) {
throw new ClassNotFoundException(name, e);
}
}
}
public class TestCustomClassLoader {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
CustomClassLoader customClassLoader = new CustomClassLoader();
Class> clazz = customClassLoader.loadClass("com.example.MyClass");
Object instance = clazz.newInstance();
System.out.println(instance.getClass().getClassLoader()); // 打印自定義類加載器
}
}
通過反射調(diào)用私有方法
通過反射調(diào)用 ClassLoader 的私有方法 findClass,直接加載類而不通過雙親委派模型。
public class ReflectionClassLoader {
public static void main(String[] args) throws Exception {
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
Class> clazz = loadClassWithoutDelegation(systemClassLoader, "com.example.MyClass");
Object instance = clazz.newInstance();
System.out.println(instance.getClass().getClassLoader()); // 打印系統(tǒng)類加載器
}
private static Class> loadClassWithoutDelegation(ClassLoader classLoader, String className) throws Exception {
// 反射獲取 findClass 方法
java.lang.reflect.Method method = ClassLoader.class.getDeclaredMethod("findClass", String.class);
method.setAccessible(true);
return (Class>) method.invoke(classLoader, className);
}
}
修改類加載器的加載路徑
通過修改類加載器的加載路徑,使得自定義類路徑優(yōu)先于父類加載器加載路徑
import java.net.URL;
import java.net.URLClassLoader;
public class CustomPathClassLoader {
public static void main(String[] args) throws Exception {
// 自定義類路徑
URL[] urls = {new URL("file:/path/to/classes/")};
URLClassLoader customClassLoader = new URLClassLoader(urls, null); // 指定 null 作為父加載器,打破雙親委派
Class> clazz = customClassLoader.loadClass("com.example.MyClass");
Object instance = clazz.newInstance();
System.out.println(instance.getClass().getClassLoader()); // 打印自定義 URLClassLoader
}
}
在這個(gè)示例中,URLClassLoader 被用來加載自定義路徑下的類,并通過將父加載器設(shè)置為 null 來打破雙親委派模型。
柚子快報(bào)激活碼778899分享:開發(fā)語言 JVM類加載器
文章鏈接
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點(diǎn)和立場(chǎng)。
轉(zhuǎn)載請(qǐng)注明,如有侵權(quán),聯(lián)系刪除。