新聞中心
竟然還有人沒搞懂 JVM 類加載器?一文徹底明白
作者:Java高級(jí)架構(gòu)師阿谷 2019-10-28 10:19:27
云計(jì)算
虛擬化 在Java面試中,在考察完項(xiàng)目經(jīng)驗(yàn)、基礎(chǔ)技術(shù)后,我會(huì)根據(jù)候選人的特點(diǎn)進(jìn)行知識(shí)深度的考察,如果候選人簡歷上有寫JVM(Java虛擬機(jī))相關(guān)的東西,那么我常常會(huì)問一些JVM的問題。

創(chuàng)新互聯(lián)建站專注于企業(yè)成都全網(wǎng)營銷、網(wǎng)站重做改版、涪陵網(wǎng)站定制設(shè)計(jì)、自適應(yīng)品牌網(wǎng)站建設(shè)、H5高端網(wǎng)站建設(shè)、商城網(wǎng)站制作、集團(tuán)公司官網(wǎng)建設(shè)、外貿(mào)網(wǎng)站建設(shè)、高端網(wǎng)站制作、響應(yīng)式網(wǎng)頁設(shè)計(jì)等建站業(yè)務(wù),價(jià)格優(yōu)惠性價(jià)比高,為涪陵等各大城市提供網(wǎng)站開發(fā)制作服務(wù)。
寫在前面
在Java面試中,在考察完項(xiàng)目經(jīng)驗(yàn)、基礎(chǔ)技術(shù)后,我會(huì)根據(jù)候選人的特點(diǎn)進(jìn)行知識(shí)深度的考察,如果候選人簡歷上有寫JVM(Java虛擬機(jī))相關(guān)的東西,那么我常常會(huì)問一些JVM的問題。JVM的類加載機(jī)制是一個(gè)很經(jīng)典的知識(shí)點(diǎn),圍繞這個(gè)知識(shí)點(diǎn)可以有下面這些難度不同的問題。
- 簡單講下JVM中的類加載過程
- JVM中的類加載和卸載的時(shí)機(jī)?
- 如何理解JVM中不同類加載器的概念和作用?
- 簡單講下JVM中的雙親委派模型?
- 什么情況下會(huì)破壞雙親委派模型?為什么?可否舉個(gè)例子?
- Tomcat中的類加載機(jī)制有了解嗎?為什么這么設(shè)計(jì)?
- 實(shí)際開發(fā)中有遇到哪些類加載器相關(guān)的問題?你又是如何解決的?
- JVM之上的弱類型語言例如Groovy是如何實(shí)現(xiàn)?簡單講下動(dòng)態(tài)類加載機(jī)制?
在接下來的幾篇文章,我將跟讀者一起重新梳理一遍類加載器的相關(guān)知識(shí),爭(zhēng)取能夠妥善解答上面列出的這些問題。
基本概念篇
類的加載和卸載
JVM是虛擬機(jī)的一種,它的指令集語言是字節(jié)碼,字節(jié)碼構(gòu)成的文件是class文件。平常我們寫的Java文件,需要編譯為class文件才能交給JVM運(yùn)行。可以這么說:C語言代碼——>二進(jìn)制文件——>計(jì)算機(jī)硬件,就相當(dāng)于Java代碼——>字節(jié)碼文件——>JVM。JVM將指定的class文件讀取到內(nèi)存里,并運(yùn)行該class文件里的Java程序的過程,就稱之為類的加載;反之,將某個(gè)class文件的運(yùn)行時(shí)數(shù)據(jù)從JVM中移除的過程,就稱之為類的卸載。
class文件的運(yùn)行時(shí)數(shù)據(jù)就是C++對(duì)象,也稱為kclass對(duì)象,這些運(yùn)行時(shí)數(shù)據(jù)在JDK7之前是放在永久代(PermGen),JDK8之后則放在元空間(Metaspace)。
類的生命周期
Java類從被虛擬機(jī)加載開始,到卸載出內(nèi)存為止,它的整個(gè)生命周期包括:加載(Loading)、驗(yàn)證(Verification)、準(zhǔn)備(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸載(Unloading)7個(gè)階段;其中驗(yàn)證、準(zhǔn)備和解析又統(tǒng)稱為連接(Linking)階段。
類的加載的時(shí)機(jī)
虛擬機(jī)規(guī)范并未嚴(yán)格規(guī)定類加載的時(shí)機(jī),跟具體的JVM虛擬機(jī)有關(guān)。類加載的最佳時(shí)機(jī)是解析Java字節(jié)碼類文件中常量池符號(hào)的時(shí)候,Class.forName()、ClassLoader.loadClass()、反射API和JNI_FindClass都可以觸發(fā)類加載,Hot JVM自身啟動(dòng)的時(shí)候也會(huì)觸發(fā)類加載。
通過JVM參數(shù)中加-verbose:class,可以在應(yīng)用啟動(dòng)的時(shí)候打印類加載的過程,如下圖所示:
- 初始化這個(gè)階段,JVM虛擬機(jī)給出了5種必須對(duì)類進(jìn)行“初始化”的情況
- 使用new關(guān)鍵字實(shí)例化對(duì)象的時(shí)候、讀取或設(shè)置一個(gè)類的靜態(tài)字段的時(shí)候、調(diào)用一個(gè)類的靜態(tài)方法的時(shí)候;
- 使用java.lang.reflect包的方法對(duì)類進(jìn)行反射調(diào)用的時(shí)候,如果類沒有進(jìn)行過初始化,則要先觸發(fā)其初始化;
- 當(dāng)初始化一個(gè)類的時(shí)候,如果發(fā)現(xiàn)其父類還沒有被初始化,則要先初始化其父類;
- 當(dāng)虛擬機(jī)啟動(dòng)時(shí),用戶需要指定一個(gè)執(zhí)行的主類(包含main方法的那個(gè)類),則虛擬機(jī)會(huì)優(yōu)先初始化這個(gè)主類;
- 在JDK1.7以后,動(dòng)態(tài)語言支持的時(shí)候,如果一個(gè)java.lang.invoke.MethodHandle實(shí)例最后的結(jié)果是要執(zhí)行第1種情況的操作,則也要進(jìn)行初始化。
類的卸載時(shí)機(jī)
類的卸載跟采用的垃圾收集算法有關(guān),在CMS中有兩種方法卸載不必要的類,一種是等到元空間(Metaspace)滿了的時(shí)候觸發(fā)FGC,另一種是使用跟CMS并發(fā)收集算法類似的方式,不過對(duì)于元空間的閾值和觸發(fā)CMS并發(fā)收集的閾值是獨(dú)立的。更具體的可以參考之前的文章:CMS學(xué)習(xí)筆記。在這里,我們只需要記住,JVM中一個(gè)類的卸載要滿足下面這3個(gè)條件:
- 該類所有的實(shí)例對(duì)象都已被回收;
- 該類的類加載器對(duì)象已經(jīng)被回收;
- 該類對(duì)應(yīng)的java.lang.Class對(duì)象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法。
類加載器的作用
類的加載是需要類加載器完成的,但是類加載器在JVM中的作用可不止這些。在JVM中,一個(gè)類的唯一性是需要這個(gè)類本身和類加載一起才能確定的,每個(gè)類加載器都有一個(gè)獨(dú)立的命名空間。
不同的類加載器,即使是同一個(gè)類字節(jié)碼文件,最后再JVM里的類對(duì)象也不是同一個(gè),下面的代碼展示了這個(gè)結(jié)論:
- package jvm;
- import java.io.IOException;
- import java.io.InputStream;
- public class ClassLoaderTest {
- public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException,
- InstantiationException {
- ClassLoader myLoader = new ClassLoader() {
- @Override
- public Class> loadClass(String name) throws ClassNotFoundException {
- String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
- InputStream inputStream = getClass().getResourceAsStream(fileName);
- if (inputStream == null) {
- return super.loadClass(name);
- }
- try {
- byte[] b = new byte[inputStream.available()];
- inputStream.read(b);
- return defineClass(name, b, 0, b.length);
- } catch (IOException e) {
- throw new ClassNotFoundException();
- }
- }
- };
- Object obj = myLoader.loadClass("jvm.ClassLoaderTest").newInstance();
- System.out.println(obj.getClass());
- System.out.println(obj instanceof jvm.ClassLoaderTest);
- ClassLoaderTest classLoaderTest = new ClassLoaderTest();
- System.out.println(classLoaderTest.getClass());
- System.out.println(classLoaderTest instanceof jvm.ClassLoaderTest);
- }
- }
上述代碼的運(yùn)行結(jié)果是:
可以看出,代碼中使用自定義類加載器(myLoader)加載的jvm.ClassLoaderTest類和通過應(yīng)用程序類加載器加載的類不是同一個(gè)類。綜上,類加載器在JVM中的作用有:
- 將類的字節(jié)碼文件從JVM外部加載到內(nèi)存中
- 確定一個(gè)類的唯一性
- 提供隔離特性,為中間件開發(fā)者提供便利,例如Tomcat
名稱欄目:竟然還有人沒搞懂JVM類加載器?一文徹底明白
網(wǎng)站網(wǎng)址:http://www.5511xx.com/article/dpohggj.html


咨詢
建站咨詢
