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