準備:
- Markdown編寫工具
- 有道雲筆記
- Idea開發工具
- GItHub項目地址
分析:
類加載運行全過程:
當我們用java命令運行某個類的main函數啓動程序時,首先需要通過類加載器把主類加載到JVM。
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();
}
}
- 其中loadClass的類加載過程有如下幾步:
- 加載 >> 驗證 >> 準備 >> 解析 >> 初始化 >> 使用 >> 卸載
- 加載:在硬盤上查找並通過IO讀入字節碼文件,使用到類時纔會加載,例如調用類的main()方法,new對象等等,在加載階段會在內存中生成一個代表這個類的java.lang.Class對象,作爲方法區這個類的各種數據的訪問入口
- 驗證:校驗字節碼文件的正確性
- 準備:給類的靜態變量分配內存,並賦予默認值
- 解析:將符號引用替換爲直接引用,該階段會把一些靜態方法(符號引用,比如main()方法)替換爲指向數據所存內存的指針或句柄等(直接引用),這是所謂的靜態鏈接過程(類加載期間完成),動態鏈接是在程序運行期間完成的將符號引用替換爲直接引用.
- 初始化:對類的靜態變量初始化爲指定的值,執行靜態代碼塊
- 類被加載到方法區中後主要包含 運行時常量池、類型信息、字段信息、方法信息、類加載器的引用、對應class實例的引用等信息。
- 類加載器的引用:這個類到類加載器實例的引用
- 對應class實例的引用:類加載器在加載類信息放到方法區中後,會創建一個對應的Class 類型的對象實例放到堆(Heap)中, 作爲開發人員訪問方法區中類定義的入口和切入點。
- *注意: * 主類在運行過程中如果使用到其它類,會逐步加載這些類。 jar包或war包裏的類不是一次性全部加載的,是使用到時才加載。
public class TestDynamicLoader {
static {
System.out.println("*************load TestDynamicLoad************");
}
public static void main(String[] args) {
new A();
System.out.println("*************load test************");
// B b = null; //B不會加載,除非這裏執行 new B()
B b = new B();
}
}
class A {
static {
System.out.println("*************load A************");
}
public A() {
System.out.println("*************initial A************");
}
}
class B {
static {
System.out.println("*************load B************");
}
public B() {
System.out.println("*************initial B************");
}
}
# 運行結果:
*************load TestDynamicLoad************
*************load A************
*************initial A************
*************load test************
*************load B************
*************initial B************
類加載器和雙親委派機制:
上面的類加載過程主要是通過類加載器來實現的,Java裏有如下幾種類加載器:
- 引導類加載器:負責加載支撐JVM運行的位於JRE的lib目錄下的核心類庫,比如rt.jar、charsets.jar等
- 擴展類加載器:負責加載支撐JVM運行的位於JRE的lib目錄下的ext擴展目錄中的JAR類包
- 應用程序類加載器:負責加載ClassPath路徑下的類包,主要就是加載你自己寫的那些類
- 自定義加載器:負責加載用戶自定義路徑下的類包
public class TestJDKClassLoader {
public static void main(String[] args) {
System.out.println(String.class.getClassLoader());
System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader().getClass().getName());
System.out.println(TestJDKClassLoader.class.getClassLoader().getClass().getName());
System.out.println();
ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
ClassLoader extClassloader = appClassLoader.getParent();
ClassLoader bootstrapLoader = extClassloader.getParent();
System.out.println("the bootstrapLoader : " + bootstrapLoader);
System.out.println("the extClassloader : " + extClassloader);
System.out.println("the appClassLoader : " + appClassLoader);
System.out.println();
System.out.println("bootstrapLoader加載以下文件:");
URL[] urls = Launcher.getBootstrapClassPath().getURLs();
for (int i = 0; i < urls.length; i++) {
System.out.println(urls[i]);
}
System.out.println();
System.out.println("extClassloader加載以下文件:");
System.out.println(System.getProperty("java.ext.dirs"));
System.out.println();
System.out.println("appClassLoader加載以下文件:");
System.out.println(System.getProperty("java.class.path"));
}
}
#運行結果:
null
sun.misc.Launcher$ExtClassLoader
sun.misc.Launcher$AppClassLoader
the bootstrapLoader : null
the extClassloader : sun.misc.Launcher$ExtClassLoader@2d209079
the appClassLoader : sun.misc.Launcher$AppClassLoader@18b4aac2
bootstrapLoader加載以下文件:
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_152.jdk/Contents/Home/jre/lib/resources.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_152.jdk/Contents/Home/jre/lib/rt.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_152.jdk/Contents/Home/jre/lib/sunrsasign.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_152.jdk/Contents/Home/jre/lib/jsse.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_152.jdk/Contents/Home/jre/lib/jce.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_152.jdk/Contents/Home/jre/lib/charsets.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_152.jdk/Contents/Home/jre/lib/jfr.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_152.jdk/Contents/Home/jre/classes
extClassloader加載以下文件:
/Users/fwh/Library/Java/Extensions:/Library/Java/JavaVirtualMachines/jdk1.8.0_152.jdk/Contents/Home/jre/lib/ext:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java
appClassLoader加載以下文件:
/Library/Java/JavaVirtualMachines/jdk1.8.0_152.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_152.jdk/Contents/Home/jre/lib/deploy.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_152.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_152.jdk/Contents/Home/jre/lib/ext/dnsns.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_152.jdk/Contents/Home/jre/lib/ext/jaccess.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_152.jdk/Contents/Home/jre/lib/ext/jfxrt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_152.jdk/Contents/Home/jre/lib/ext/localedata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_152.jdk/Contents/Home/jre/lib/ext/nashorn.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_152.jdk/Contents/Home/jre/lib/ext/sunec.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_152.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_152.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_152.jdk/Contents/Home/jre/lib/ext/zipfs.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_152.jdk/Contents/Home/jre/lib/javaws.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_152.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_152.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_152.jdk/Contents/Home/jre/lib/jfxswt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_152.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_152.jdk/Contents/Home/jre/lib/management-agent.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_152.jdk/Contents/Home/jre/lib/plugin.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_152.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_152.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_152.jdk/Contents/Home/lib/ant-javafx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_152.jdk/Contents/Home/lib/dt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_152.jdk/Contents/Home/lib/javafx-mx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_152.jdk/Contents/Home/lib/jconsole.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_152.jdk/Contents/Home/lib/packager.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_152.jdk/Contents/Home/lib/sa-jdi.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_152.jdk/Contents/Home/lib/tools.jar:/Users/fwh/A_FWH/GItHub/fwh-JVM/target/classes:/Users/fwh/.m2/repository/org/projectlombok/lombok/1.18.12/lombok-1.18.12.jar:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar:/Users/fwh/Library/Caches/JetBrains/IntelliJIdea2020.1/captureAgent/debugger-agent.jar
Disconnected from the target VM, address: '127.0.0.1:50876', transport: 'socket'
類加載器初始化過程:
參見類運行加載全過程圖可知其中會創建JVM啓動器實例sun.misc.Launcher。
sun.misc.Launcher初始化使用了單例模式設計,保證一個JVM虛擬機內只有一個sun.misc.Launcher實例。 在Launcher構造方法內部,其創建了兩個類加載器,分別是sun.misc.Launcher.ExtClassLoader(擴展類加載器)和sun.misc.Launcher.AppClassLoader(應用類加載器)。
JVM默認使用Launcher的getClassLoader()方法返回的類加載器AppClassLoader的實例加載我們的應用程序。
以下來自Java8版本:
//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");
。。。 。。。 //省略一些不需關注代碼
雙親委派機制:
- JVM類加載器是有親子層級結構的,如下圖:
這裏類加載其實就有一個雙親委派機制,加載某個類時會先委託父加載器尋找目標類,找不到再委託上層父加載器加載,如果所有父加載器在自己的加載類路徑下都找不到目標類,則在自己的類加載路徑中查找並載入目標類。
比如我們的Math類,最先會找應用程序類加載器加載,應用程序類加載器會先委託擴展類加載器加載,擴展類加載器再委託引導類加載器,頂層引導類加載器在自己的類加載路徑裏找了半天沒找到Math類,則向下退回加載Math類的請求,擴展類加載器收到回覆就自己加載,在自己的類加載路徑裏找了半天也沒找到Math類,又向下退回Math類的加載請求給應用程序類加載器,應用程序類加載器於是在自己的類加載路徑裏找Math類,結果找到了就自己加載了。。
雙親委派機制說簡單點就是,先找父親加載,不行再由兒子自己加載
我們來看下應用程序類加載器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;
}
}
爲什麼要設計雙親委派機制?
- 沙箱安全機制:自己寫的java.lang.String.class類不會被加載,這樣便可以防止核心API庫被隨意篡改
- 避免類的重複加載:當父親已經加載了該類時,就沒有必要子ClassLoader再加載一次,保證被加載類的唯一性 看一個類加載示例:
package java.lang;
public class String {
public static void main(String[] args) {
System.out.println("**************My String Class**************");
}
}
運行結果:
錯誤: 在類 java.lang.String 中找不到 main 方法, 請將 main 方法定義爲:
public static void main(String[] args)
否則 JavaFX 應用程序類必須擴展javafx.application.Application
全盤負責委託機制
- “全盤負責”是指當一個ClassLoder裝載一個類時,除非顯示的使用另外一個ClassLoder,該類所依賴及引用的類也由這個ClassLoder載入。
自定義類加載器示例:
- 自定義類加載器只需要繼承 java.lang.ClassLoader 類,該類有兩個核心方法,
- 一個是loadClass(String, boolean),實現了雙親委派機制,
- 還有一個方法是findClass,默認實現是空方法,所以我們自定義類加載器主要是重寫findClass方法。
public class MyClassLoaderTest {
static class MyClassLoader extends ClassLoader {
private String classPath;
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
private byte[] loadByte(String name) throws Exception {
name = name.replaceAll("\\.", "/");
FileInputStream fis = new FileInputStream(classPath + "/" + name
+ ".class");
int len = fis.available();
byte[] data = new byte[len];
fis.read(data);
fis.close();
return data;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte(name);
//defineClass將一個字節數組轉爲Class對象,這個字節數組是class文件讀取後最終的字節數組。
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
}
public static void main(String args[]) throws Exception {
//初始化自定義類加載器,會先初始化父類ClassLoader,其中會把自定義類加載器的父加載器設置爲應用程序類加載器AppClassLoader
MyClassLoader classLoader = new MyClassLoader("D:/test");
//D盤創建 test/com/tuling/jvm 幾級目錄,將User類的複製類User1.class丟入該目錄
Class clazz = classLoader.loadClass("com.tuling.jvm.User1");
Object obj = clazz.newInstance();
Method method = clazz.getDeclaredMethod("sout", null);
method.invoke(obj, null);
System.out.println(clazz.getClassLoader().getClass().getName());
}
}
運行結果:
=======自己的加載器加載類調用方法=======
com.tuling.jvm.MyClassLoaderTest$MyClassLoader
- 此處注意:需要加載是主類,需要在指定目錄下有Object.class。
- 但由於沙箱安全機制,肯定是會異常的,需要手動邏輯判斷下代碼,重新賦值。
打破雙親委派機制:
- 但由於沙箱安全機制,肯定是會異常的,需要手動邏輯判斷下代碼,重新賦值。
- 再來一個沙箱安全機制示例,嘗試打破雙親委派機制,用自定義類加載器加載我們自己實現的 java.lang.String.class
public class MyClassLoaderTest2 {
static class MyClassLoader extends ClassLoader {
private String classPath;
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
private byte[] loadByte(String name) throws Exception {
name = name.replaceAll("\\.", "/");
FileInputStream fis = new FileInputStream(classPath + "/" + name
+ ".class");
int len = fis.available();
byte[] data = new byte[len];
fis.read(data);
fis.close();
return data;
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte(name);
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
/**
* 重寫類加載方法,實現自己的加載邏輯,不委派給雙親加載
*
* @param name
* @param resolve
* @return
* @throws ClassNotFoundException
*/
@Override
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
}
public static void main(String args[]) throws Exception {
MyClassLoader classLoader = new MyClassLoader("D:/test");
//嘗試用自己改寫類加載機制去加載自己寫的java.lang.String.class
Class clazz = classLoader.loadClass("java.lang.String");
Object obj = clazz.newInstance();
Method method = clazz.getDeclaredMethod("sout", null);
method.invoke(obj, null);
System.out.println(clazz.getClassLoader().getClass().getName());
}
}
運行結果:
java.lang.SecurityException: Prohibited package name: java.lang
at java.lang.ClassLoader.preDefineClass(ClassLoader.java:659)
at java.lang.ClassLoader.defineClass(ClassLoader.java:758)
Tomcat打破雙親委派機制:
以Tomcat類加載爲例,Tomcat 如果使用默認的雙親委派類加載機制行不行?
我們思考一下:Tomcat是個web容器, 那麼它要解決什麼問題:
- 一個web容器可能需要部署兩個應用程序,不同的應用程序可能會依賴同一個第三方類庫的不同版本,不能要求同一個類庫在同一個服務器只有一份,因此要保證每個應用程序的類庫都是獨立的,保證相互隔離。
- 部署在同一個web容器中相同的類庫相同的版本可以共享。否則,如果服務器有10個應用程序,那麼要有10份相同的類庫加載進虛擬機。
- web容器也有自己依賴的類庫,不能與應用程序的類庫混淆。基於安全考慮,應該讓容器的類庫和程序的類庫隔離開來。
- web容器要支持jsp的修改,我們知道,jsp 文件最終也是要編譯成class文件才能在虛擬機中運行,但程序運行後修改jsp已經是司空見慣的事情, web容器需要支持 jsp 修改後不用重啓。
再看看我們的問題:Tomcat 如果使用默認的雙親委派類加載機制行不行?
答案是不行的。爲什麼?
- 第一個問題,如果使用默認的類加載器機制,那麼是無法加載兩個相同類庫的不同版本的,默認的類加器是不管你是什麼版本的,只在乎你的全限定類名,並且只有一份。
- 第二個問題,默認的類加載器是能夠實現的,因爲他的職責就是保證唯一性。
- 第三個問題和第一個問題一樣。
- 我們再看第四個問題,我們想我們要怎麼實現jsp文件的熱加載,jsp 文件其實也就是class文件,那麼如果修改了,但類名還是一樣,類加載器會直接取方法區中已經存在的,修改後的jsp是不會重新加載的。那麼怎麼辦呢?我們可以直接卸載掉這jsp文件的類加載器,所以你應該想到了,每個jsp文件對應一個唯一的類加載器,當一個jsp文件修改了,就直接卸載這個jsp類加載器。重新創建類加載器,重新加載jsp文件。
Tomcat自定義加載器詳解:
tomcat的幾個主要類加載器:
- commonLoader:Tomcat最基本的類加載器,加載路徑中的class可以被Tomcat容器本身以及各個Webapp訪問;
- catalinaLoader:Tomcat容器私有的類加載器,加載路徑中的class對於Webapp不可見;
- sharedLoader:各個Webapp共享的類加載器,加載路徑中的class對於所有Webapp可見,但是對於Tomcat容器不可見;
- WebappClassLoader:各個Webapp私有的類加載器,加載路徑中的class只對當前Webapp可見,比如加載war包裏相關的類,每個war包應用都有自己的WebappClassLoader,實現相互隔離,比如不同war包應用引入了不同的spring版本,這樣實現就能加載各自的spring版本;
從圖中的委派關係中可以看出:
- CommonClassLoader能加載的類都可以被CatalinaClassLoader和SharedClassLoader使用,從而實現了公有類庫的共用,而CatalinaClassLoader和SharedClassLoader自己能加載的類則與對方相互隔離。 WebAppClassLoader可以使用SharedClassLoader加載到的類,但各個WebAppClassLoader實例之間相互隔離。
- 而JasperLoader的加載範圍僅僅是這個JSP文件所編譯出來的那一個.Class文件,它出現的目的就是爲了被丟棄:當Web容器檢測到JSP文件被修改時,會替換掉目前的JasperLoader的實例,並通過再建立一個新的Jsp類加載器來實現JSP文件的熱加載功能。
tomcat 這種類加載機制違背了java 推薦的雙親委派模型了嗎?答案是:違背了。
- 很顯然,tomcat 不是這樣實現,tomcat 爲了實現隔離性,沒有遵守這個約定,每個webappClassLoader加載自己的目錄下的class文件,不會傳遞給父類加載器,打破了雙親委派機制。
模擬實現Tomcat的webappClassLoader加載自己war包應用內不同版本類實現相互共存與隔離
public class MyClassLoaderTest3 {
static class MyClassLoader extends ClassLoader {
private String classPath;
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
private byte[] loadByte(String name) throws Exception {
name = name.replaceAll("\\.", "/");
FileInputStream fis = new FileInputStream(classPath + "/" + name
+ ".class");
int len = fis.available();
byte[] data = new byte[len];
fis.read(data);
fis.close();
return data;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte(name);
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
/**
* 重寫類加載方法,實現自己的加載邏輯,不委派給雙親加載
* @param name
* @param resolve
* @return
* @throws ClassNotFoundException
*/
@Override
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
//非自定義的類還是走雙親委派加載
if (!name.startsWith("com.tuling.jvm")){
c = this.getParent().loadClass(name);
}else{
c = findClass(name);
}
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
}
public static void main(String args[]) throws Exception {
MyClassLoader classLoader = new MyClassLoader("D:/test");
Class clazz = classLoader.loadClass("com.tuling.jvm.User1");
Object obj = clazz.newInstance();
Method method= clazz.getDeclaredMethod("sout", null);
method.invoke(obj, null);
System.out.println(clazz.getClassLoader());
System.out.println();
MyClassLoader classLoader1 = new MyClassLoader("D:/test1");
Class clazz1 = classLoader1.loadClass("com.tuling.jvm.User1");
Object obj1 = clazz1.newInstance();
Method method1= clazz1.getDeclaredMethod("sout", null);
method1.invoke(obj1, null);
System.out.println(clazz1.getClassLoader());
}
}
*注意:同一個JVM內,兩個相同包名和類名的類對象可以共存,因爲他們的類加載器可以不一樣,所以看兩個類對象是否是同一個,除了看類的包名和類名是否都相同之外,還需要他們的類加載器也是同一個才能認爲他們是同一個。 *
相關知識點:
- javap -private Person.class
- 反編譯輸出查看其字節碼文件的內容;
- 主要加載是ClassLoad的loadClass
- JVM默認使用Launcher的getClassLoader()方法返回的類加載器AppClassLoader的實例加載我們的應用程序。
總結:
- JVM內容是Java開發者必備知識點,很多底層性能調優時候都需要具備這方面的知識儲備,才能更快速的定位問題點找到方法。
- 示例代碼-GitHub
- Blog鏈接:文浩的博客
-
微信公衆號: