错误: 找不到或无法加载主类 helloworld_全面剖析虚拟机类加载机制
1.引言
java源文件經(jīng)過(guò)編譯后生成字節(jié)碼class文件,需要經(jīng)過(guò)虛擬機(jī)加載并轉(zhuǎn)換成匯編指令才能執(zhí)行,那么虛擬機(jī)是如何一步步加載這些class文件的對(duì)于java程序員是完全透明的,本文嘗試全面分析jvm類加載機(jī)制。
2.思考
開(kāi)始之前我們來(lái)簡(jiǎn)單思考一下,如果讓你來(lái)寫虛擬機(jī)類加載你覺(jué)得要怎么做?
首先,肯定有一個(gè)加載過(guò)程,虛擬機(jī)要讀取class字節(jié)碼。
其次,為了保證虛擬機(jī)的安全性,需要對(duì)輸入做校驗(yàn),只有校驗(yàn)通過(guò)了才能繼續(xù)執(zhí)行,程序設(shè)計(jì)總是這樣,才能保證系統(tǒng)安全穩(wěn)定。
再次,校驗(yàn)通過(guò)后將字節(jié)碼轉(zhuǎn)換成類對(duì)象。
最后,將類對(duì)象建立全局索引方便引用。
如果把類加載也當(dāng)成一個(gè)工程子模塊,從邏輯上看,我們上面的分析沒(méi)有什么問(wèn)題,但工程實(shí)踐經(jīng)驗(yàn)表明,實(shí)際情況肯定要復(fù)雜一些,因?yàn)殡S著深入,總有新問(wèn)題產(chǎn)生,至于復(fù)雜多少需要我們繼續(xù)深入分析。
3.類的生命周期
類從被加載到j(luò)vm內(nèi)存開(kāi)始,到卸載出內(nèi)存需要經(jīng)過(guò)7個(gè)階段:加載(Loading)、驗(yàn)證(Verification)、準(zhǔn)備(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸載(Unloading)。
類生命周期的七個(gè)階段并非串行執(zhí)行,比如在進(jìn)行驗(yàn)證時(shí)與此同時(shí)準(zhǔn)備階段或解析階段已經(jīng)開(kāi)始了,階段是相互嵌套并行執(zhí)行的,只是按照邏輯分類可以這樣進(jìn)行區(qū)分。又比如正常情況下解析階段過(guò)后是初始化階段,但為了支持java語(yǔ)言的運(yùn)行時(shí)綁定(也成為動(dòng)態(tài)綁定或晚期綁定),在初始化階段之后才開(kāi)始解析階段。
類的主動(dòng)引用和被動(dòng)引用
什么情況下開(kāi)始類加載呢,一般下面四種情況必須立即進(jìn)行類初始化工作,如果類加載沒(méi)做自然也必須立馬做:
以上4種情況稱為對(duì)類的主動(dòng)引用。
除了以上4種情況,其他對(duì)類的引用被稱為被動(dòng)引用,
case1:子類引用父類的的靜態(tài)字段,只會(huì)觸發(fā)父類的初始化而不會(huì)觸發(fā)子類初始化。
public class SuperClass {public static int value = 123; static {System.out.println("SuperClass init!");} } public class SubClass extends SuperClass {static {System.out.println("SubClass init!");} } public class NotInitialization {public static void main(String[] args) {System.out.println(SubClass.value);} }上述代碼運(yùn)行之后,最后輸出的是“SuperClass init!”。
case2:通過(guò)數(shù)組定義來(lái)引用類,不會(huì)觸發(fā)此類的初始化
public class NotInitialization {public static void main(String[] args) {SuperClass[] superArray = new SuperClass[10];} }這段代碼并不會(huì)觸發(fā)SuperClass初始化,即不會(huì)輸出“SuperClass init!”。
注:這段代碼會(huì)觸發(fā)[LSuperClass初始化,這個(gè)類代表SuperClass一維數(shù)組,相對(duì)c/c++,java對(duì)一維數(shù)組的封裝抱枕了安全性,當(dāng)數(shù)組發(fā)生越界時(shí),將拋出java.lang.ArrayIndexOutOfBoundsException。
case3:常量在編譯階段會(huì)存入調(diào)用類的常量池中,本質(zhì)上沒(méi)有直接引用到定義常量的類,因此不會(huì)觸發(fā)定義常量的類的初始化。
public class ConstClass {public static final String HELLOWORLD = "Hello World"; static {System.out.println("ConstClass init!");} } public class NotInitialization {public static void main(String[] args) {System.out.println(ConstClass.HELLOWORLD);} }對(duì)常量ConstClass.HELLOWORLD的引用實(shí)際被轉(zhuǎn)換成NotInitialization類自身常量池的引用。
接口加載過(guò)程差異點(diǎn)
接口和類加載過(guò)程略有不同,根本差異點(diǎn)在于:當(dāng)一個(gè)類初始化時(shí),要求其父類全部已經(jīng)初始化過(guò)了,但在一個(gè)接口初始化時(shí),并不要求其父接口全部初始化,只有在真正使用到父接口時(shí)才會(huì)初始化。
4.類加載過(guò)程
類加載過(guò)程包含5個(gè)階段:加載、驗(yàn)證、準(zhǔn)備、解析和初始化。下面來(lái)分析一下這5個(gè)階段JVM都做了什么。
加載階段
加載階段需要完成3個(gè)事:
虛擬機(jī)規(guī)范并沒(méi)有要求二進(jìn)制字節(jié)流要從哪里獲取,也就是說(shuō),在加載階段是開(kāi)了口子的,很開(kāi)放的,富有創(chuàng)造性的程序員在這個(gè)舞臺(tái)玩出了各種花樣,比如字節(jié)流獲取可以從:
還有其他方式,只有你想不到的。
相對(duì)于類加載其他階段的透明性,加載階段是程序員可控性最強(qiáng)的階段,因?yàn)榧虞d階段可以使用系統(tǒng)提供的類加載器,也可以用戶自定義類加載器,我們?cè)谙挛倪€會(huì)詳細(xì)說(shuō)明Java的類加載器。
驗(yàn)證階段
驗(yàn)證階段屬于連接階段的第一步,是出于虛擬機(jī)自身安全考慮,確保二進(jìn)制字節(jié)流包含的信息符合虛擬機(jī)的要求。這也說(shuō)明了Java語(yǔ)言是相對(duì)安全的語(yǔ)言,使用純粹的Java代碼無(wú)法做到諸如訪問(wèn)數(shù)據(jù)邊界以外的數(shù)據(jù),將一個(gè)對(duì)象轉(zhuǎn)換成一個(gè)未知類型,跳轉(zhuǎn)到不存在的代碼行之類的行為。
驗(yàn)證階段一般需要完成4個(gè)階段的校驗(yàn)過(guò)程:文件格式校驗(yàn)、元數(shù)據(jù)校驗(yàn)、字節(jié)碼校驗(yàn)和符號(hào)引用校驗(yàn)。
文件格式校驗(yàn)
文件格式校驗(yàn)主要是完成語(yǔ)法校驗(yàn),即檢查二進(jìn)制字節(jié)流是否符合Class文件格式規(guī)范,目標(biāo)是保證輸入的字節(jié)流能正確地解析并存儲(chǔ)于方法區(qū)之內(nèi),格式上符合描述Java類型信息的要求。
校驗(yàn)項(xiàng)具體包含:
經(jīng)過(guò)了這層驗(yàn)證,字節(jié)流才會(huì)進(jìn)入方法區(qū)內(nèi)存儲(chǔ)。我們也可以看到驗(yàn)證階段其實(shí)是和加載階段交織在一起的。
元數(shù)據(jù)校驗(yàn)
元數(shù)據(jù)校驗(yàn)階段是對(duì)字節(jié)碼信息進(jìn)行語(yǔ)義分析,以保證數(shù)據(jù)符合Java語(yǔ)言規(guī)范,比如是否繼承了一個(gè)不允許被繼承的父類。具體驗(yàn)證點(diǎn)包含:
元數(shù)據(jù)階段主要是完成數(shù)據(jù)類型校驗(yàn)。
字節(jié)碼校驗(yàn)
字節(jié)碼驗(yàn)證階段將對(duì)類的方法體(數(shù)據(jù)流和控制流)進(jìn)行驗(yàn)證分析,是整個(gè)驗(yàn)證階段最復(fù)雜的階段。這個(gè)階段保證方法在運(yùn)行時(shí)不會(huì)出現(xiàn)危害虛擬機(jī)安全的行為。比如需要做:
但方法體內(nèi)邏輯校驗(yàn)無(wú)法做到絕對(duì)可靠,即不能指望校驗(yàn)程序準(zhǔn)確地檢查出程序能否在有限時(shí)間之內(nèi)結(jié)束運(yùn)行。
符號(hào)引用校驗(yàn)
符號(hào)引用校驗(yàn)可以看做是對(duì)類自身以外的信息進(jìn)行匹配性校驗(yàn),發(fā)生時(shí)機(jī)是虛擬機(jī)將符號(hào)引用轉(zhuǎn)換成直接引用時(shí)。校驗(yàn)內(nèi)容包含:
符號(hào)引用驗(yàn)證的目的是保證解析動(dòng)作能正常執(zhí)行,如果沒(méi)有通過(guò)符號(hào)引用驗(yàn)證將拋出java.lang.IncompatibleClassChangeError異常的子類,比如java.lang.IllegalAccessError、java.lang.NoSuchFieldError、java.lang.NoSuchMethodError等。
以上是驗(yàn)證階段校驗(yàn)的內(nèi)容。
準(zhǔn)備階段
準(zhǔn)備階段正式為類變量分配內(nèi)存并設(shè)置類變量的初始值,這些變量將在方法區(qū)中進(jìn)行分配,也就是準(zhǔn)備階段分配的是類變量,并非實(shí)例變量。
準(zhǔn)備階段初始化類變量零值,以下是基本類型的零值:
舉個(gè)例子,假設(shè)一個(gè)類變量定義如下:
public static int value = 123;那么value在準(zhǔn)備階段初始化value = 0而非123,那什么時(shí)候會(huì)變成123呢,把value變成123的是putstatic指令是存放在類構(gòu)造器<clinit>()方法中,而類構(gòu)造器方法在初始化階段才會(huì)執(zhí)行。
但也存在特殊情況類變量賦值不是0的情況,比如在類中定義常量,如果類字段的字段表屬性表中存在ConstantValue屬性,則在準(zhǔn)備階段就會(huì)賦值。
public static final int value = 123;解析階段
解析階段是虛擬機(jī)將常量池內(nèi)的符號(hào)引用(Symbolic References)替換為直接引用(Direct References)的過(guò)程。符號(hào)引用在符號(hào)引用驗(yàn)證中提到過(guò),它以CONSTANT_Class_info,CONSTANT_Fieldref_info、CONSTANT_Methodref_info及CONSTANT_InterfaceMethodref_info等類型的常量出現(xiàn)。符號(hào)引用和直接引用的差別在于:
- 符號(hào)引用是以一組符號(hào)來(lái)描述所引用的目標(biāo),符號(hào)可以是任何形式的字面量,只要使用時(shí)能無(wú)歧義地定位到目標(biāo)即可。符號(hào)引用與虛擬機(jī)實(shí)現(xiàn)的內(nèi)部布局無(wú)關(guān),引用的目標(biāo)不一定已經(jīng)加載到內(nèi)存中。
- 直接引用是可以直接指向目標(biāo)的指針、相對(duì)偏移量或是一個(gè)能間接定位到目標(biāo)的句柄。
解析什么時(shí)候發(fā)生呢,虛擬機(jī)并未明確指定,一般虛擬機(jī)實(shí)現(xiàn)會(huì)根據(jù)需要來(lái)判斷,解析可能發(fā)生在類被加載器加載時(shí)就對(duì)常量池中的符號(hào)引用進(jìn)行解析,亦或等到一個(gè)符號(hào)引用將要使用前才去解析。
此外,對(duì)同一個(gè)符號(hào)的多次解析請(qǐng)求是很常見(jiàn)的,為了避免重復(fù)解析,虛擬機(jī)實(shí)現(xiàn)可能會(huì)對(duì)第一次解析的結(jié)果緩存,即在運(yùn)行時(shí)常量池中記錄直接引用,并把常量標(biāo)識(shí)為已解析狀態(tài)。
解析動(dòng)作主要針對(duì)類、接口、字段、類方法、接口方法四類符號(hào)引用。
類或接口的解析過(guò)程
假設(shè)當(dāng)前代碼所處的類是D,如果要把一個(gè)從未解析過(guò)的符號(hào)引用N解析為一個(gè)類或接口C的直接引用,那虛擬機(jī)完成整個(gè)解析的過(guò)程需要包含以下3個(gè)步驟:
字段解析
解析一個(gè)未被解析過(guò)的字段前,首先需要對(duì)字段表內(nèi)class_index(即字段的類索引)的CONSTANT_Class_info符號(hào)引用進(jìn)行解析,也就是或在進(jìn)行字段解析之前,需要先完成類或接口的符號(hào)解析。
假設(shè)一個(gè)需要解析的字段所屬的類或接口為C,虛擬機(jī)規(guī)范要求按如下步驟對(duì)C后序字段進(jìn)行搜索:
如果在父類和接口中存在同名字段會(huì)發(fā)生什么呢?如果是這樣情況,編譯器將拒絕編譯,比如下面這種情況:
public class FieldResolution {interface Interface0 {int a = 0;} interface Interface1 extends Interface0 {int a = 1;} interface Interface2 {int a = 2;}static class Parent implements Interface1 {public static int a = 3;} static class Sub extends Parent implements Interface2 {public static int a = 4;} public static void main(String[] args) {System.out.println(Sub.a);} }
若將Sub靜態(tài)成員變量?publc static int a = 4?注釋掉,編譯器將返回“The field Sub.A is ambiguous”。
下面我們將類方法解析和接口方法解析,兩者是分開(kāi)的。
類方法解析
類解析和字段解析一樣,需要先解析出類方法表的class_index索引所屬類或接口的符號(hào)引用。如果類或接口符號(hào)引用接口成功,我們依然用C來(lái)表示類或接口,接下來(lái)將按如下步驟進(jìn)行搜索:
查找結(jié)束若成功返回,還需要對(duì)方法權(quán)限進(jìn)行校驗(yàn)。
接口方法解析
與類方法解析類似,步驟如下:
以上是解析階段所做的工作。
初始化階段
類初始化階段是類加載過(guò)程的最后一步,到了初始化階段,才真正執(zhí)行類中定義的字節(jié)碼。在準(zhǔn)備階段,變量已經(jīng)賦過(guò)一次初始值,初始化階段將執(zhí)行類構(gòu)造器<clinit>()方法的過(guò)程。
- <clinit>()方法是由編譯器自動(dòng)收集類中的所有類變量的賦值動(dòng)作和靜態(tài)語(yǔ)句塊static{}塊中的語(yǔ)句結(jié)合產(chǎn)生的。編譯器收集的順序是由語(yǔ)句在源文件中出現(xiàn)的順序決定的,比如靜態(tài)語(yǔ)句塊只能訪問(wèn)定義在靜態(tài)語(yǔ)句塊之前的變量。
- <clinit>()方法和類的構(gòu)造方法不同,并不需要顯示的調(diào)用父類構(gòu)造器,虛擬機(jī)保證在子類<clinit>()方法執(zhí)行之前,父類構(gòu)造器先執(zhí)行,因此,在虛擬機(jī)中第一個(gè)執(zhí)行類構(gòu)造器的類為java.lang.Object。
- <clinit>()方法對(duì)于類或接口來(lái)說(shuō)并不是必須的。
- 虛擬機(jī)會(huì)保證一個(gè)類的<clinit>()方法在多線程環(huán)境中被正確地加鎖和同步。
5.類加載器(ClassLoader)
上一節(jié)我們?cè)谝婚_(kāi)始就聊到虛擬機(jī)允許用戶自定義類加載器,這就給java語(yǔ)言帶來(lái)了很大的靈活性,類加載器可以說(shuō)是Java語(yǔ)言的一項(xiàng)創(chuàng)新,也是Java語(yǔ)言流行的重要原因之一。類加載器在類層次劃分、OSGi、熱部署、代碼加密等領(lǐng)域大放異彩,成為Java技術(shù)體系的一塊重要基石。
類和類加載器
類的唯一性是有類加載器決定的,比較2各類是否相等除了比較類本身還需要比較類加載器。如果一個(gè)類被2個(gè)不同的類加載器加載,那么加載的類是2個(gè)不同的類。如下所示:
public class ClassLoaderTest {public static void main(String[] args) {ClassLoader myLoader = new ClassLoader() {@Overridepublic Class<?> loadClass(String name) throws ClassNotFoundException {try {String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";InputStream is = this.getClass().getResourceAsStream(fileName);if (is == null) {return super.loadClass(name);}byte[] b = new byte[is.available()];is.read(b);return defineClass(name, b,, 0, b.length);}catch (IOException e) {throw new ClassNotFoundException(name);}}};Object obj = myLoader.loadClass("ClassLoaderTest").newInstance();System.out.println(obj instanceof ClassLoaderTest);} }運(yùn)行結(jié)果:false。
類加載器的層次結(jié)構(gòu)
“橫看成嶺側(cè)成峰”,站在Java虛擬機(jī)角度看,只存在2種不同類型的類加載器:一種是啟動(dòng)類加載器(Bootstrap ClassLoader),這個(gè)類加載器是C++實(shí)現(xiàn)的,是虛擬機(jī)自身一部分;另外一種是其他類加載器,這種類加載器都是虛擬機(jī)外部的,由Java實(shí)現(xiàn),并且全部繼承自抽象類java.lang.ClassLoader。站在Java程序員角度看,可以分成以下3種:
以下是類加載器的層次結(jié)構(gòu)
雙親委派模型
類加載器雙親委派模型是JDK1.2引入被應(yīng)用于幾乎所有的Java程序中。但它并不是一個(gè)強(qiáng)制性的約束模型,二是Java設(shè)計(jì)者推薦的一種類加載方式。
雙親委派有他的適用場(chǎng)景(它能夠適用于絕大多數(shù)場(chǎng)景),模型可以保證Java程序的穩(wěn)定運(yùn)行,防止重復(fù)加載和任意修改。那具體是如何做到的呢?
雙親委派模型的工作過(guò)程如下:如果一個(gè)類加載器收到了類加載的請(qǐng)求,它首先不會(huì)自己嘗試加載這個(gè)類,而是把請(qǐng)求委派給父類加載器去完成,如上圖所示,每一層次的類加載器都是如此,因此所有的加載請(qǐng)求最終都應(yīng)該傳送到頂層的啟動(dòng)類加載器中,只有父類反饋?zhàn)约簾o(wú)法完成這個(gè)加載請(qǐng)求時(shí)(即它的搜索范圍沒(méi)有找到指定的類),字加載器才會(huì)嘗試自己加載。
比如java.lang.Object,無(wú)論哪個(gè)類加載器需要加載這個(gè)類,最終都由Bootstrap ClassLoader加載,因此Object在程序的各個(gè)類加載器環(huán)境都是同一個(gè)類。相反,如果如果不用雙親委派模型進(jìn)行加載,用戶自定義了一個(gè)Object類并放置在類路徑下,最終可能會(huì)引發(fā)程序混亂。
雙親委派模型很好地解決了基礎(chǔ)類的統(tǒng)一問(wèn)題,保證了虛擬機(jī)的安全性。
非雙親委派模型
線程上下文類加載器
雙親委派模型適用于大部分場(chǎng)景,但也有它自身的缺陷,假設(shè)基礎(chǔ)類由Bootstrap類加載器加載,但是基礎(chǔ)類需要回調(diào)用戶的代碼,基礎(chǔ)代碼卻是由應(yīng)用類加載器加載,這個(gè)時(shí)候該怎么辦呢?
JNDI(Java Naming and Directory Interface)服務(wù)就是上面描述的這種場(chǎng)景,JNDI是Java的標(biāo)準(zhǔn)服務(wù),它自身的代碼由Bootstrap類加載器加載,由于JNDI的目的就是對(duì)資源進(jìn)行集中管理和查找,它需要調(diào)用獨(dú)立廠商實(shí)現(xiàn)的JNDI接口提供者(SPI)的代碼,獨(dú)立廠商提供的代碼jar包放置在ClassPath下,如果使用雙親委派模型加載類的方式是搞不定的,怎么辦呢?
為了解決這個(gè)困境,Java設(shè)計(jì)團(tuán)隊(duì)引入了線程上下文類加載器(Thread Context ClassLoader),雖然它確實(shí)不太優(yōu)雅,但解決問(wèn)題啊。這個(gè)類加載器可以通過(guò)java.lang.Thread類的setContextClassLoader()方法進(jìn)行設(shè)置,如果創(chuàng)建線程還未設(shè)置,它將從父線程中繼承一個(gè),如果在應(yīng)用程序的全局范圍內(nèi)都沒(méi)有設(shè)置過(guò),那么這個(gè)類加載器就是AppClassLoader。有了線程上下文類加載器,JNDI服務(wù)就可以加載所需要的SPI代碼,即父類加載器可以請(qǐng)求子類加載器完成類加載動(dòng)作,這其實(shí)是違反了雙親委派原則的。實(shí)際上JNDI、JDBC、JCE、JAXB、JBI等所有涉及SPI加載動(dòng)作的基本都采取的這種方式。
總結(jié):線程上下文加載器之所以打破雙親委派模型是因?yàn)殡p親委派模型依賴的單一方向的,并不能解決父類加載器去依賴子類加載器這種逆方向需求。
Tomcat類加載器
實(shí)際上,不只是Driver驅(qū)動(dòng)的實(shí)現(xiàn)是這樣,只要有需要,在雙親委派機(jī)制無(wú)法滿足需求前提下,在tomcat、spring等等的容器框架也是通過(guò)一些手段繞過(guò)雙親委派機(jī)制。
雙親委派模型要求除了頂層的啟動(dòng)類加載器之外,其余的類加載器都應(yīng)當(dāng)由自己的父類加載器加載。tomcat 為了實(shí)現(xiàn)隔離性,沒(méi)有遵守這個(gè)約定,每個(gè)webappClassLoader加載自己的目錄下的class文件,不會(huì)傳遞給父類加載器。如下圖所示
從圖中的委派關(guān)系中可以看出:
- CommonClassLoader能加載的類都可以被Catalina ClassLoader和SharedClassLoader使用,從而實(shí)現(xiàn)了公有類庫(kù)的共用。
- CatalinaClassLoader和Shared ClassLoader自己能加載的類則與對(duì)方相互隔離。
- WebAppClassLoader可以使用SharedClassLoader加載到的類,但各個(gè)WebAppClassLoader實(shí)例之間相互隔離。
JasperLoader的加載范圍僅僅是這個(gè)JSP文件所編譯出來(lái)的那一個(gè).Class文件,它出現(xiàn)的目的就是為了被丟棄:當(dāng)Web容器檢測(cè)到JSP文件被修改時(shí),會(huì)替換掉目前的JasperLoader的實(shí)例,并通過(guò)再建立一個(gè)新的Jsp類加載器來(lái)實(shí)現(xiàn)JSP文件的HotSwap功能。
總結(jié):tomcat之所以破壞雙親委派模型,我想主要在于雙親委派模型只看到了共享性,沒(méi)有看到隔離性需求,即共享是有條件的共享。
OSGI類加載器
非雙親委派模型的另一種需求來(lái)自程序動(dòng)態(tài)性追求。比如代碼熱替換(HotSwap)、模塊熱部署(Hot Deployment)。可以哪USB熱插拔技術(shù)來(lái)做比方。熱部署對(duì)生產(chǎn)系統(tǒng)來(lái)說(shuō)具有很大吸引力,不用停機(jī)就能完成部署效率啊。
OSGI是Java模塊化標(biāo)準(zhǔn),OSGI實(shí)現(xiàn)模塊熱部署的關(guān)鍵是它自定義的類加載器,每一個(gè)模塊都有一個(gè)自己定義的類加載器,當(dāng)需要更換一個(gè)Bundle時(shí),則把Bundle連同類加載器一同替換以實(shí)現(xiàn)熱替換。
在OSGI環(huán)境下,類加載器不再是樹(shù)型結(jié)構(gòu)的雙親委派模型,而是網(wǎng)狀結(jié)構(gòu),當(dāng)收到類加載請(qǐng)求時(shí),OSGI是按照下面順序進(jìn)行類搜索的:
如果以上都未查詢到,則查找失敗。
上面的搜索順序除了1,2兩點(diǎn)和雙親委派類似,其余都是平級(jí)類加載過(guò)程。
總結(jié):OSGI Boundle類加載器提供了類加載的另一種機(jī)制,加載器結(jié)構(gòu)不一定非得是樹(shù)型結(jié)構(gòu),也可以是網(wǎng)狀結(jié)構(gòu)。
全文總結(jié)
本文較全面的分析了jvm的類加載機(jī)制,分析了類加載的5個(gè)階段,包含:加載、驗(yàn)證、準(zhǔn)備、解析、初始化,最后總結(jié)了類加載器加載類的幾種模型:雙親委派模型、SPI的類加載模型、tomcat類加載模型以及OSGI類加載模型。
The end.
轉(zhuǎn)載請(qǐng)注明來(lái)源,否則嚴(yán)禁轉(zhuǎn)載。
總結(jié)
以上是生活随笔為你收集整理的错误: 找不到或无法加载主类 helloworld_全面剖析虚拟机类加载机制的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Nexus下载安装及对接
- 下一篇: 【渗透过程】嘉缘网站 --测试