讓類(lèi)活起來(lái)——漫談JVM類(lèi)加載機(jī)制

JVM類(lèi)加載機(jī)制,點(diǎn)擊查看原圖

所謂類(lèi)加載機(jī)制,就是虛擬機(jī)把描述類(lèi)的數(shù)據(jù)從Class文件加載到內(nèi)存中,并對(duì)其進(jìn)行校驗(yàn),轉(zhuǎn)換,分析以及初始化,并最終形成虛擬機(jī)可以被使用java類(lèi)型的過(guò)程。

Java作為解釋型語(yǔ)言,支持動(dòng)態(tài)加載動(dòng)態(tài)連接,類(lèi)型的加載、連接以及初始化過(guò)程都在程序運(yùn)行是完成,雖然這樣會(huì)導(dǎo)致類(lèi)加載的過(guò)程變慢,但是為Java語(yǔ)言提供了更好的靈活性,實(shí)現(xiàn)了動(dòng)態(tài)的擴(kuò)展。

1. 類(lèi)加載概述

1.1 類(lèi)的生命周期

類(lèi)從被加載到虛擬機(jī)內(nèi)存中開(kāi)始,到卸載出內(nèi)存為止,它的整個(gè)生命周期包括:加載驗(yàn)證、準(zhǔn)備、解析初始化、使用卸載七個(gè)階段。

其中類(lèi)加載的過(guò)程包括了裝載、驗(yàn)證準(zhǔn)備、解析、初始化五個(gè)階段。其中驗(yàn)證、準(zhǔn)備、解析三個(gè)步驟又合稱(chēng)為連接

類(lèi)加載的過(guò)程

在這五個(gè)階段中,加載、驗(yàn)證、準(zhǔn)備和初始化這四個(gè)階段發(fā)生的順序是確定的,而解析階段則不一定,它在某些情況下可以在初始化階段之后開(kāi)始,這是為了支持Java語(yǔ)言的運(yùn)行時(shí)綁定(也成為動(dòng)態(tài)綁定或晚期綁定)。

這里簡(jiǎn)要說(shuō)明下Java中的綁定:綁定指的是把一個(gè)方法的調(diào)用與方法所在的類(lèi)(方法主體)關(guān)聯(lián)起來(lái),對(duì)java來(lái)說(shuō),綁定分為靜態(tài)綁定和動(dòng)態(tài)綁定:

  • 靜態(tài)綁定:即前期綁定。在程序執(zhí)行前方法已經(jīng)被綁定,此時(shí)由編譯器或其它連接程序?qū)崿F(xiàn)。針對(duì)java,簡(jiǎn)單的可以理解為程序編譯期的綁定。java當(dāng)中的方法只有final,static,private構(gòu)造方法是前期綁定的。
  • 動(dòng)態(tài)綁定:即晚期綁定,也叫運(yùn)行時(shí)綁定。在運(yùn)行時(shí)根據(jù)具體對(duì)象的類(lèi)型進(jìn)行綁定。在java中,幾乎所有的方法都是后期綁定的。

1.2 類(lèi)文件從何而來(lái)

既然加載機(jī)制是虛擬機(jī)把描述類(lèi)的數(shù)據(jù)從Class文件加載到內(nèi)存中的過(guò)程,那Class文件從何而來(lái)?

類(lèi)文件來(lái)源包括

  • 從本地文件系統(tǒng)加載的class文件
  • 從JAR包加載class文件
    從網(wǎng)絡(luò)加載class文件
  • 把一個(gè)Java源文件動(dòng)態(tài)編譯,并執(zhí)行加載

1.3 何時(shí)執(zhí)行類(lèi)的初始化

JVM規(guī)范中沒(méi)有明確說(shuō)明合適開(kāi)始類(lèi)的加載,但是指明一下情況下必須要對(duì)類(lèi)經(jīng)行初始化(加載、驗(yàn)證、準(zhǔn)備等階段自然要在這之前進(jìn)行):

  1. 創(chuàng)建類(lèi)實(shí)例。也就是new的方式;
  2. 調(diào)用某個(gè)類(lèi)的類(lèi)方法(靜態(tài)方法,invokeStatic指令碼);
  3. 訪問(wèn)某個(gè)類(lèi)或接口的類(lèi)變量(getStatic指令碼),或?yàn)樵擃?lèi)變量賦值(putStatic指令碼);
  4. 使用反射方式強(qiáng)制創(chuàng)建某個(gè)類(lèi)或接口對(duì)應(yīng)的java.lang.Class對(duì)象;
  5. 初始化某個(gè)類(lèi)的子類(lèi),則其父類(lèi)也會(huì)被初始化;
  6. 直接使用java.exe命令來(lái)運(yùn)行某個(gè)主類(lèi)(含有Main函數(shù));

2. 類(lèi)加載的過(guò)程

2.1 裝載

裝載是查找并加載類(lèi)的二進(jìn)制數(shù)據(jù)(查找和導(dǎo)入Class文件)的過(guò)程。作為類(lèi)加載過(guò)程的第一個(gè)階段,在裝載階段,JVM需要完成以下三件事情:

  1. 通過(guò)一個(gè)類(lèi)的全限定名來(lái)獲取其定義的二進(jìn)制字節(jié)流;

  2. 將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu);

  3. Java堆中生成一個(gè)代表這個(gè)類(lèi)的java.lang.Class對(duì)象,作為對(duì)方法區(qū)中這些數(shù)據(jù)的訪問(wèn)入口。

開(kāi)發(fā)人員既可以使用系統(tǒng)提供的類(lèi)加載器來(lái)完成加載,也可以自定義自己的類(lèi)加載器來(lái)完成加載。這部分內(nèi)容在后面的章節(jié)介紹。

2.2 連接

類(lèi)的加載過(guò)程后生成了類(lèi)的java.lang.Class對(duì)象,接著會(huì)進(jìn)入連接階段,連接階段負(fù)責(zé)將類(lèi)的二進(jìn)制數(shù)據(jù)合并入JRE(Java運(yùn)行時(shí)環(huán)境)中。類(lèi)的連接大致分三個(gè)階段。

  • 驗(yàn)證:檢驗(yàn)被加載的類(lèi)是否有正確的內(nèi)部結(jié)構(gòu),并和其他類(lèi)協(xié)調(diào)一致;
  • 準(zhǔn)備:負(fù)責(zé)為類(lèi)的類(lèi)變量分配內(nèi)存,并設(shè)置默認(rèn)初始值;
  • 解析:將類(lèi)的二進(jìn)制數(shù)據(jù)中的符號(hào)引用替換成直接引用;

2.2.1 驗(yàn)證

驗(yàn)證的目的是確保被加載的類(lèi)的正確性

驗(yàn)證是連接階段的第一步,這一階段的目的是為了確保Class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會(huì)危害虛擬機(jī)自身的安全。驗(yàn)證階段大致會(huì)完成4個(gè)階段的檢驗(yàn)動(dòng)作:

  • 文件格式驗(yàn)證:驗(yàn)證字節(jié)流是否符合Class文件格式的規(guī)范;驗(yàn)證通過(guò)之后,裝載階段獲得字節(jié)流才會(huì)保存到方法區(qū);

  • 元數(shù)據(jù)驗(yàn)證:對(duì)字節(jié)碼描述的信息進(jìn)行語(yǔ)義分析(注意:對(duì)比javac編譯階段的語(yǔ)義分析),以保證其描述的信息符合Java語(yǔ)言規(guī)范的要求;例如:這個(gè)類(lèi)是否有父類(lèi),除了java.lang.Object之外。

  • 字節(jié)碼驗(yàn)證:通過(guò)數(shù)據(jù)流和控制流分析,確定程序語(yǔ)義是合法的、符合邏輯的。

  • 符號(hào)引用驗(yàn)證:它發(fā)生在虛擬機(jī)將符號(hào)引用轉(zhuǎn)化為直接引用的時(shí)候(解析階段中發(fā)生該轉(zhuǎn)化,后面會(huì)有講解),主要是對(duì)類(lèi)自身以外的信息(常量池中的各種符號(hào)引用)進(jìn)行匹配性的校驗(yàn)。

驗(yàn)證階段是非常重要的,但不是必須的,它對(duì)程序運(yùn)行期沒(méi)有影響,如果所引用的類(lèi)經(jīng)過(guò)反復(fù)驗(yàn)證,那么可以考慮采用-Xverifynone參數(shù)來(lái)關(guān)閉大部分的類(lèi)驗(yàn)證措施,以縮短虛擬機(jī)類(lèi)加載的時(shí)間。

2.2.2 準(zhǔn)備

準(zhǔn)備:為類(lèi)的靜態(tài)變量分配內(nèi)存,并將其初始化為默認(rèn)值。

準(zhǔn)備階段是正式為類(lèi)變量分配內(nèi)存并設(shè)置類(lèi)變量初始值的階段,這些內(nèi)存都將在方法區(qū)中分配。對(duì)于該階段有以下幾點(diǎn)需要注意:

  1. 這時(shí)候進(jìn)行內(nèi)存分配的僅包括類(lèi)變量(static),而不包括實(shí)例變量,實(shí)例變量會(huì)在對(duì)象實(shí)例化時(shí)隨著對(duì)象一塊分配在Java堆中。

  2. 這里所設(shè)置的初始值通常情況下是數(shù)據(jù)類(lèi)型默認(rèn)的零值(如0、0L、null、false等),而不是被在Java代碼中被顯式地賦予的值。

假設(shè)一個(gè)類(lèi)變量的定義為:public static int value = 3; 那么變量value在準(zhǔn)備階段過(guò)后的初始值為0,而不是3,因?yàn)檫@時(shí)候尚未開(kāi)始執(zhí)行任何Java方法,而把value賦值為3的putstatic指令是在程序編譯后,存放于類(lèi)構(gòu)造器<clinit>()方法之中的,所以把value賦值為3的動(dòng)作將在初始化階段才會(huì)執(zhí)行。

Java中所有基本數(shù)據(jù)類(lèi)型以及reference類(lèi)型的默認(rèn)零值

2.2.3 解析

解析:把類(lèi)中的符號(hào)引用轉(zhuǎn)換為直接引用。

解析階段是虛擬機(jī)將常量池內(nèi)的符號(hào)引用替換為直接引用的過(guò)程,解析動(dòng)作主要針對(duì)類(lèi)或接口、字段、類(lèi)方法、接口方法、方法類(lèi)型、方法句柄和調(diào)用限定符7類(lèi)符號(hào)引用進(jìn)行。

  • 符號(hào)引用就是一組符號(hào)來(lái)描述目標(biāo),可以是任何字面量;
  • 直接引用就是直接指向目標(biāo)的指針、相對(duì)偏移量或一個(gè)間接定位到目標(biāo)的句柄。

2.3 初始化

初始化,即對(duì)類(lèi)的靜態(tài)變量,靜態(tài)代碼塊執(zhí)行初始化操作。這是類(lèi)加載過(guò)程的最后一步,到了此階段,才真正開(kāi)始執(zhí)行類(lèi)中定義的Java程序代碼

初始化為類(lèi)的靜態(tài)變量賦予正確的初始值,在Java中對(duì)類(lèi)變量進(jìn)行初始值設(shè)定有兩種方式:

  • 聲明類(lèi)變量是指定初始值。
  • 使用靜態(tài)代碼塊為類(lèi)變量指定初始值。

類(lèi)的初始化步驟 / JVM初始化步驟:

  1. 如果這個(gè)類(lèi)還沒(méi)有被加載和鏈接,那先進(jìn)行加載和鏈接

  2. 假如這個(gè)類(lèi)存在直接父類(lèi),并且這個(gè)類(lèi)還沒(méi)有被初始化(注意:在一個(gè)類(lèi)加載器中,類(lèi)只能初始化一次),那就初始化直接的父類(lèi)(不適用于接口)

  3. 假如類(lèi)中存在初始化語(yǔ)句(如static變量和static塊),那就依次執(zhí)行這些初始化語(yǔ)句。

另一方面,初始化階段是執(zhí)行類(lèi)構(gòu)造器<clinit>()方法的過(guò)程:

  • <clinit>()方法是由編譯器自動(dòng)收集類(lèi)中的所有類(lèi)變量的賦值動(dòng)作和靜態(tài)語(yǔ)句塊中的語(yǔ)句合并產(chǎn)生的,編譯器收集的順序是由語(yǔ)句在源文件中出現(xiàn)的順序所決定的;
  • JVM會(huì)保證每個(gè)類(lèi)的<clinit>()都只執(zhí)行一遍,不會(huì)被反復(fù)加載;
  • JVM保證<clinit>()執(zhí)行過(guò)程中的多線程安全;

3. 類(lèi)加載器

類(lèi)的加載器是Java語(yǔ)言的一種創(chuàng)新。

3.1 類(lèi)與類(lèi)加載器之間的關(guān)系

對(duì)于任意一個(gè)類(lèi),都需要由它的類(lèi)加載器和這個(gè)類(lèi)本身一同確定其在就Java虛擬機(jī)中的唯一性,也就是說(shuō),即使兩個(gè)類(lèi)來(lái)源于同一個(gè)Class文件,只要加載它們的類(lèi)加載器不同,那這兩個(gè)類(lèi)就必定不相等。這里的“相等”包括了代表類(lèi)的Class對(duì)象的equals()、isAssignableFrom()isInstance()等方法的返回結(jié)果,也包括了使用instanceof關(guān)鍵字對(duì)對(duì)象所屬關(guān)系的判定結(jié)果。

3.2 類(lèi)加載器的分類(lèi)

站在Java虛擬機(jī)的角度來(lái)講,只存在兩種不同的類(lèi)加載器:

  • 啟動(dòng)類(lèi)加載器:它使用C++實(shí)現(xiàn)(這里僅限于Hotspot,也就是JDK1.5之后默認(rèn)的虛擬機(jī),有很多其他的虛擬機(jī)是用Java語(yǔ)言實(shí)現(xiàn)的),是虛擬機(jī)自身的一部分。
  • 所有其他的類(lèi)加載器:這些類(lèi)加載器都由Java語(yǔ)言實(shí)現(xiàn),獨(dú)立于虛擬機(jī)之外,并且全部繼承自抽象類(lèi)java.lang.ClassLoader,這些類(lèi)加載器需要由啟動(dòng)類(lèi)加載器加載到內(nèi)存中之后才能去加載其他的類(lèi)。
類(lèi)加載器分類(lèi)

站在Java開(kāi)發(fā)人員的角度來(lái)看,類(lèi)加載器可以大致劃分為以下三類(lèi):

  • 啟動(dòng)類(lèi)加載器:Bootstrap ClassLoader,跟上面相同。它負(fù)責(zé)加載存放在$JAVA_HOME/jre/lib/rt.jar 里所有的class或-Xbootclassoath選項(xiàng)指定的jar包。由C++實(shí)現(xiàn),不是ClassLoader子類(lèi)。
    啟動(dòng)類(lèi)加載器是無(wú)法被Java程序直接引用的。
  • 擴(kuò)展類(lèi)加載器:Extension ClassLoader,該加載器由sun.misc.Launcher$ExtClassLoader實(shí)現(xiàn),它負(fù)責(zé)加載java平臺(tái)中擴(kuò)展功能的一些jar包,比如$JAVA_HOME\jre\lib\ext目錄中,或者由java.ext.dirs系統(tǒng)變量指定的路徑中的所有類(lèi)庫(kù),開(kāi)發(fā)者可以直接使用擴(kuò)展類(lèi)加載器。
  • 應(yīng)用程序類(lèi)加載器:Application ClassLoader,該類(lèi)加載器由sun.misc.Launcher$AppClassLoader來(lái)實(shí)現(xiàn),它負(fù)責(zé)加載classpath中指定的jar包及 Djava.class.path 所指定目錄下的類(lèi)和jar包。開(kāi)發(fā)者可以直接使用該類(lèi)加載器,如果應(yīng)用程序中沒(méi)有自定義過(guò)自己的類(lèi)加載器,一般情況下這個(gè)就是程序中默認(rèn)的類(lèi)加載器

3.3 雙親委派模型

應(yīng)用程序都是由以上三種類(lèi)加載器互相配合進(jìn)行加載的,如果有必要,我們還可以加入自定義的類(lèi)加載器。

加載器之間存在著層次關(guān)系,如下所示:


加載器的層次關(guān)系

這種層次關(guān)系稱(chēng)為類(lèi)加載器的雙親委派模型。注意這里是以組合關(guān)系復(fù)用父類(lèi)加載器的父子關(guān)系,而不是以繼承關(guān)系實(shí)現(xiàn)的。

類(lèi)加載器的雙親委派加載機(jī)制:當(dāng)一個(gè)類(lèi)收到了類(lèi)加載請(qǐng)求,他首先不會(huì)嘗試自己去加載這個(gè)類(lèi),而是把這個(gè)請(qǐng)求委派給父類(lèi)去完成,每一個(gè)層次類(lèi)加載器都是如此,因此所有的加載請(qǐng)求都應(yīng)該傳送到啟動(dòng)類(lèi)加載其中,只有當(dāng)父類(lèi)加載器反饋?zhàn)约簾o(wú)法完成這個(gè)請(qǐng)求的時(shí)候(在它的加載路徑下沒(méi)有找到所需加載的Class),子類(lèi)加載器才會(huì)嘗試自己去加載。

以下代碼可以驗(yàn)證類(lèi)加載器之間的父子層次關(guān)系

public class ClassLoaderTest {
    public static void main(String[] args) {
        //獲取系統(tǒng)/應(yīng)用類(lèi)加載器
        ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println("系統(tǒng)/應(yīng)用類(lèi)加載器:" + appClassLoader);
        //獲取系統(tǒng)/應(yīng)用類(lèi)加載器的父類(lèi)加載器,得到擴(kuò)展類(lèi)加載器
        ClassLoader extcClassLoader = appClassLoader.getParent();
        System.out.println("擴(kuò)展類(lèi)加載器" + extcClassLoader);
        System.out.println("擴(kuò)展類(lèi)加載器的加載路徑:" + System.getProperty("java.ext.dirs"));
        //獲取擴(kuò)展類(lèi)加載器的父加載器,但因根類(lèi)加載器并不是用Java實(shí)現(xiàn)的所以不能獲取
        System.out.println("擴(kuò)展類(lèi)的父類(lèi)加載器:" + extcClassLoader.getParent());
    }
}

輸出如下:

系統(tǒng)/應(yīng)用類(lèi)加載器:sun.misc.Launcher$AppClassLoader@7f31245a
擴(kuò)展類(lèi)加載器sun.misc.Launcher$ExtClassLoader@45ee12a7
擴(kuò)展類(lèi)加載器的加載路徑:/Users/Library/Java/Extensions:/Library/Java/JavaVirtualMachines/jdk1.8.0_40.jdk/Contents/Home/jre/lib/ext:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java
擴(kuò)展類(lèi)的父類(lèi)加載器:null

為什么根類(lèi)加載器為NULL?
根類(lèi)加載器并不是Java實(shí)現(xiàn)的,而且由于程序通常須訪問(wèn)根加載器,因此訪問(wèn)擴(kuò)展類(lèi)加載器的父類(lèi)加載器時(shí)返回NULL。

使用雙親委派模型來(lái)組織類(lèi)加載器之間的關(guān)系,有一個(gè)很明顯的好處,就是Java類(lèi)隨著它的類(lèi)加載器(說(shuō)白了,就是它所在的目錄)一起具備了一種帶有優(yōu)先級(jí)的層次關(guān)系,這對(duì)于保證Java程序的穩(wěn)定運(yùn)作很重要,保證同一個(gè)類(lèi)在不同的環(huán)境中都由同一個(gè)類(lèi)加載器來(lái)加載,保證一致性。

3.4 自定義類(lèi)加載器

JVM中除了根類(lèi)加載器之外的所有類(lèi)的加載器都是ClassLoader子類(lèi)的實(shí)例,通過(guò)重寫(xiě)ClassLoader中的方法,實(shí)現(xiàn)自定義的類(lèi)加載器

  • loadClass(String name,boolean resolve): 為ClassLoader的入口點(diǎn),根據(jù)指定名稱(chēng)來(lái)加載類(lèi),系統(tǒng)就是調(diào)用ClassLoader的該方法來(lái)獲取制定類(lèi)對(duì)應(yīng)的Class對(duì)象
  • findClass(String name):根據(jù)指定名稱(chēng)來(lái)查找類(lèi)

下面是實(shí)現(xiàn)findClass方法的自定義類(lèi)加載器的實(shí)例:

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Method;

public class MyClassLoader extends ClassLoader {
    // 讀取一個(gè)文件的內(nèi)容
    @SuppressWarnings("resource")
    private byte[] getBytes(String filename) throws IOException{
        File file = new File(filename);
        long len = file.length();
        byte[] raw = new byte[(int) len];
        FileInputStream fin = new FileInputStream(file);

        // 一次讀取class文件的全部二進(jìn)制數(shù)據(jù)
        int r = fin.read(raw);
        if (r != len)
            throw new IOException("無(wú)法讀取全部文件" + r + "!=" + len);
        fin.close();
        return raw;
    }

    // 定義編譯指定java文件的方法
    private boolean compile(String javaFile) throws IOException {
        System.out.println("CompileClassLoader:正在編譯" + javaFile + "……..");
        // 調(diào)用系統(tǒng)的javac命令
        Process p = Runtime.getRuntime().exec("javac" + javaFile);
        try {
            // 其它線程都等待這個(gè)線程完成
            p.waitFor();
        } catch (InterruptedException ie) {
            System.out.println(ie);
        }

        // 獲取javac 的線程的退出值
        int ret = p.exitValue();
        // 返回編譯是否成功
        return ret == 0;
    }

    // 重寫(xiě)Classloader的findCLass方法

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class clazz = null;
        // 將包路徑中的.替換成斜線/
        String fileStub = name.replace(".", "/");
        String javaFilename = fileStub + ".java";
        String classFilename = fileStub + ".class";
        File javaFile = new File(javaFilename);
        File classFile = new File(classFilename);

        // 當(dāng)指定Java源文件存在,且class文件不存在,或者Java源文件的修改時(shí)間比class文件//修改時(shí)間晚時(shí),重新編譯
        if (javaFile.exists() && (!classFile.exists())
                || javaFile.lastModified() > classFile.lastModified()) {

            try {
                // 如果編譯失敗,或該Class文件不存在
                if (!compile(javaFilename) || !classFile.exists()) {
                    throw new ClassNotFoundException("ClassNotFoundException:"
                            + javaFilename);
                }
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }

        // 如果class文件存在,系統(tǒng)負(fù)責(zé)將該文件轉(zhuǎn)化成class對(duì)象
        if (classFile.exists()) {
            try {
                // 將class文件的二進(jìn)制數(shù)據(jù)讀入數(shù)組
                byte[] raw = getBytes(classFilename);
                // 調(diào)用Classloader的defineClass方法將二進(jìn)制數(shù)據(jù)轉(zhuǎn)換成class對(duì)象
                clazz = defineClass(name, raw, 0, raw.length);
            } catch (IOException ie) {
                ie.printStackTrace();
            }
        }

        // 如果claszz為null,表明加載失敗,則拋出異常
        if (clazz == null) {
            throw new ClassNotFoundException(name);

        }
        return clazz;
    }

    // 定義一個(gè)主方法

    public static void main(String[] args) throws Exception {
        // 如果運(yùn)行該程序時(shí)沒(méi)有參數(shù),即沒(méi)有目標(biāo)類(lèi)
        if (args.length < 1) {
            System.out.println("缺少運(yùn)行的目標(biāo)類(lèi),請(qǐng)按如下格式運(yùn)行java源文件:");
            System.out.println("java CompileClassLoader ClassName");
        }

        // 第一個(gè)參數(shù)是需要運(yùn)行的類(lèi)
        String progClass = args[0];
        // 剩下的參數(shù)將作為運(yùn)行目標(biāo)類(lèi)時(shí)的參數(shù),所以將這些參數(shù)復(fù)制到一個(gè)新數(shù)組中
        String progargs[] = new String[args.length - 1];
        System.arraycopy(args, 1, progargs, 0, progargs.length);
        MyClassLoader cl = new MyClassLoader();

        // 加載需要運(yùn)行的類(lèi)
        Class<?> clazz = cl.loadClass(progClass);
        // 獲取需要運(yùn)行的類(lèi)的主方法
        Method main = clazz.getMethod("main", (new String[0]).getClass());
        Object argsArray[] = { progargs };
        main.invoke(null, argsArray);

    }

}


參考文章

  1. 【深入Java虛擬機(jī)】之四:類(lèi)加載機(jī)制
  2. Java類(lèi)加載機(jī)制
  3. JAVA類(lèi)加載機(jī)制全解析
  4. hotpot java虛擬機(jī)Class對(duì)象是放在 方法區(qū) 還是堆中 ?
  5. JVM類(lèi)加載機(jī)制詳解(二)類(lèi)加載器與雙親委派模型
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容