JVM性能優(yōu)化--類加載器,手動實現(xiàn)類的熱加載

一褪储、類加載的機制的層次結(jié)構(gòu)

每個編寫的”.java”拓展名類文件都存儲著需要執(zhí)行的程序邏輯蛾茉,這些”.java”文件經(jīng)過Java編譯器編譯成拓展名為”.class”的文件,”.class”文件中保存著Java代碼經(jīng)轉(zhuǎn)換后的虛擬機指令帽馋,當需要使用某個類時,虛擬機將會加載它的”.class”文件,并創(chuàng)建對應的class對象绽族,將class文件加載到虛擬機的內(nèi)存姨涡,這個過程稱為類加載,這里我們需要了解一下類加載的過程吧慢,如下:

Jvm執(zhí)行class文件


file

步驟一涛漂、類加載機制

將class文件字節(jié)碼內(nèi)容加載到內(nèi)存中,并將這些靜態(tài)數(shù)據(jù)轉(zhuǎn)換成方法區(qū)中的運行時數(shù)據(jù)結(jié)構(gòu)检诗,在堆中生成一個代表這個類的java.lang.Class對象匈仗,作為方法區(qū)類數(shù)據(jù)的訪問入口,這個過程需要類加載器參與逢慌。

當系統(tǒng)運行時悠轩,類加載器將.class文件的二進制數(shù)據(jù)從外部存儲器(如光盤,硬盤)調(diào)入內(nèi)存中攻泼,CPU再從內(nèi)存中讀取指令和數(shù)據(jù)進行運算火架,并將運算結(jié)果存入內(nèi)存中。內(nèi)存在該過程中充當著"二傳手"的作用忙菠,通俗的講何鸡,如果沒有內(nèi)存,類加載器從外部存儲設備調(diào)入.class文件二進制數(shù)據(jù)直接給CPU處理只搁,而由于CPU的處理速度遠遠大于調(diào)入數(shù)據(jù)的速度音比,容易造成數(shù)據(jù)的脫節(jié),所以需要內(nèi)存起緩沖作用氢惋。

類將.class文件加載至運行時的方法區(qū)后洞翩,會在堆中創(chuàng)建一個Java.lang.Class對象,用來封裝類位于方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu)焰望,該Class對象是在加載類的過程中創(chuàng)建的骚亿,每個類都對應有一個Class類型的對象,Class類的構(gòu)造方法是私有的熊赖,只有JVM能夠創(chuàng)建来屠。因此Class對象是反射的入口,使用該對象就可以獲得目標類所關(guān)聯(lián)的.class文件中具體的數(shù)據(jù)結(jié)構(gòu)震鹉。


file

類加載的最終產(chǎn)物就是位于堆中的Class對象(注意不是目標類對象)俱笛,該對象封裝了類在方法區(qū)中的數(shù)據(jù)結(jié)構(gòu),并且向用戶提供了訪問方法區(qū)數(shù)據(jù)結(jié)構(gòu)的接口传趾,即Java反射的接口迎膜。

步驟二、連接過程

將java類的二進制代碼合并到JVM的運行狀態(tài)之中的過程

驗證:確保加載的類信息符合JVM規(guī)范浆兰,沒有安全方面的問題

準備:正式為類變量(static變量)分配內(nèi)存并設置類變量初始值的階段磕仅,這些內(nèi)存都將在方法區(qū)中進行分配

解析:虛擬機常量池的符號引用替換為字節(jié)引用過程

步驟三珊豹、初始化

初始化階段是執(zhí)行類構(gòu)造器<clinit>()方法的過程。類構(gòu)造器<clinit>()方法是由編譯器自動收藏類中的所有類變量的賦值動作和靜態(tài)語句塊(static塊)中的語句合并產(chǎn)生榕订,代碼從上往下執(zhí)行店茶。

當初始化一個類的時候,如果發(fā)現(xiàn)其父類還沒有進行過初始化劫恒,則需要先觸發(fā)其父類的初始化

虛擬機會保證一個類的<clinit>()方法在多線程環(huán)境中被正確加鎖和同步
當范圍一個Java類的靜態(tài)域時贩幻,只有真正聲名這個域的類才會被初始化

二、類加載器的層次結(jié)構(gòu)

啟動(Bootstrap)類加載器

擴展(Extension)類加載器

系統(tǒng)(-)類加載器

file

1兼贸、啟動(Bootstrap)類加載器

啟動類加載器主要加載的是JVM自身需要的類段直,這個類加載使用C++語言實現(xiàn)的,是虛擬機自身的一部分溶诞,它負責將<JAVA_HOME>/lib路徑下的核心類庫或-Xbootclasspath參數(shù)指定的路徑下的jar包加載到內(nèi)存中鸯檬,注意必由于虛擬機是按照文件名識別加載jar包的,如rt.jar螺垢,如果文件名不被虛擬機識別喧务,即使把jar包丟到lib目錄下也是沒有作用的(出于安全考慮,Bootstrap啟動類加載器只加載包名為java枉圃、javax功茴、sun等開頭的類)。

2孽亲、擴展(Extension)類加載器

擴展類加載器是指Sun公司(已被Oracle收購)實現(xiàn)的sun.misc.Launcher$ExtClassLoader類坎穿,由Java語言實現(xiàn)的,是Launcher的靜態(tài)內(nèi)部類返劲,它負責加載<JAVA_HOME>/lib/ext目錄下或者由系統(tǒng)變量-Djava.ext.dir指定位路徑中的類庫玲昧,開發(fā)者可以直接使用標準擴展類加載器。

3篮绿、系統(tǒng)(System)類加載器

也稱應用程序加載器是指 Sun公司實現(xiàn)的sun.misc.Launcher$AppClassLoader孵延。它負責加載系統(tǒng)類路徑java -classpath或-D java.class.path 指定路徑下的類庫,也就是我們經(jīng)常用到的classpath路徑亲配,開發(fā)者可以直接使用系統(tǒng)類加載器尘应,一般情況下該類加載是程序中默認的類加載器,通過ClassLoader#getSystemClassLoader()方法可以獲取到該類加載器吼虎。

在Java的日常應用程序開發(fā)中犬钢,類的加載幾乎是由上述3種類加載器相互配合執(zhí)行的,在必要時玷犹,我們還可以自定義類加載器,需要注意的是箱舞,Java虛擬機對class文件采用的是按需加載的方式拳亿,也就是說當需要使用該類時才會將它的class文件加載到內(nèi)存生成class對象,而且加載某個類的class文件時电湘,Java虛擬機采用的是雙親委派模式即把請求交由父類處理鹅经,它一種任務委派模式,下面我們進一步了解它贷痪。

3.1蹦误、理解雙親委派模式

下面我們從代碼層面了解幾個Java中定義的類加載器及其雙親委派模式的實現(xiàn),它們類圖關(guān)系如下

file

雙親委派模式是在Java 1.2后引入的舱沧,其工作原理的是偶洋,如果一個類加載器收到了類加載請求,它并不會自己先去加載玄窝,而是把這個請求委托給父類的加載器去執(zhí)行哆料,如果父類加載器還存在其父類加載器缸剪,則進一步向上委托杏节,依次遞歸典阵,請求最終將到達頂層的啟動類加載器,如果父類加載器可以完成類加載任務壮啊,就成功返回歹啼,倘若父類加載器無法完成此加載任務座菠,子加載器才會嘗試自己去加載藤树,這就是雙親委派模式,即每個兒子都很懶升略,每次有活就丟給父親去干屡限,直到父親說這件事我也干不了時,兒子自己想辦法去完成翰撑,這不就是傳說中的實力坑爹鞍⊙搿?那么采用這種模式有啥用呢?

3.1册养、雙親委派模式優(yōu)勢

采用雙親委派模式的是好處是Java類隨著它的類加載器一起具備了一種帶有優(yōu)先級的層次關(guān)系压固,通過這種層級關(guān)可以避免類的重復加載,當父親已經(jīng)加載了該類時坎炼,就沒有必要子ClassLoader再加載一次拦键。其次是考慮到安全因素,java核心api中定義類型不會被隨意替換萄金,假設通過網(wǎng)絡傳遞一個名為java.lang.Integer的類媚朦,通過雙親委托模式傳遞到啟動類加載器,而啟動類加載器在核心Java API發(fā)現(xiàn)這個名字的類孙乖,發(fā)現(xiàn)該類已被加載,并不會重新加載網(wǎng)絡傳遞的過來的java.lang.Integer唯袄,而直接返回已加載過的Integer.class恋拷,這樣便可以防止核心API庫被隨意篡改∶仿樱可能你會想阎抒,如果我們在classpath路徑下自定義一個名為java.lang.SingleInterge類(該類是胡編的)呢消痛?該類并不存在java.lang中且叁,經(jīng)過雙親委托模式逞带,傳遞到啟動類加載器中纱新,由于父類加載器路徑下并沒有該類,所以不會加載遇汞,將反向委托給子類加載器加載簿废,最終會通過系統(tǒng)類加載器加載該類。但是這樣做是不允許族檬,因為java.lang是核心API包单料,需要訪問權(quán)限,強制加載將會報出如下異常

java.lang.SecurityException: Prohibited package name: java.lang

所以無論如何都無法加載成功的递鹉。

三藏斩、類加載器間的關(guān)系

我們進一步了解類加載器間的關(guān)系(并非指繼承關(guān)系),主要可以分為以下4點

  • 啟動類加載器媳拴,由C++實現(xiàn),沒有父類塞关。
  • 拓展類加載器(ExtClassLoader)子巾,由Java語言實現(xiàn)线梗,父類加載器為null
  • 系統(tǒng)類加載器(AppClassLoader),由Java語言實現(xiàn)瘾婿,父類加載器為ExtClassLoader
  • 自定義類加載器,父類加載器肯定為AppClassLoader偏陪。

1笛谦、類加載器常用方法

loadClass(String)

該方法加載指定名稱(包括包名)的二進制類型昌阿,該方法在JDK1.2之后不再建議用戶重寫但用戶可以直接調(diào)用該方法宝泵,loadClass()方法是ClassLoader類自己實現(xiàn)的,該方法中的邏輯就是雙親委派模式的實現(xiàn)儿奶,其源碼如下闯捎,loadClass(String name, boolean resolve)是一個重載方法,resolve參數(shù)代表是否生成class對象的同時進行解析相關(guān)操作秉版。

正如loadClass方法所展示的清焕,當類加載請求到來時,先從緩存中查找該類對象秸妥,如果存在直接返回粥惧,如果不存在則交給該類加載去的父加載器去加載,倘若沒有父加載則交給頂級啟動類加載器去加載起惕,最后倘若仍沒有找到惹想,則使用findClass()方法去加載(關(guān)于findClass()稍后會進一步介紹)饵婆。從loadClass實現(xiàn)也可以知道如果不想重新定義加載類的規(guī)則侨核,也沒有復雜的邏輯搓译,只想在運行時加載自己指定的類锋喜,那么我們可以直接使用this.getClass().getClassLoder.loadClass("className")嘿般,這樣就可以直接調(diào)用ClassLoader的loadClass方法獲取到class對象炉奴。

findClass(String)

在JDK1.2之前,在自定義類加載時赛糟,總會去繼承ClassLoader類并重寫loadClass方法璧南,從而實現(xiàn)自定義的類加載類司倚,但是在JDK1.2之后已不再建議用戶去覆蓋loadClass()方法对湃,而是建議把自定義的類加載邏輯寫在findClass()方法中拍柒,從前面的分析可知,findClass()方法是在loadClass()方法中被調(diào)用的拆讯,當loadClass()方法中父加載器加載失敗后种呐,則會調(diào)用自己的findClass()方法來完成類加載爽室,這樣就可以保證自定義的類加載器也符合雙親委托模式阔墩。需要注意的是ClassLoader類中并沒有實現(xiàn)findClass()方法的具體代碼邏輯,取而代之的是拋出ClassNotFoundException異常,同時應該知道的是findClass方法通常是和defineClass方法一起使用的(稍后會分析)

defineClass(byte[] b, int off, int len)

defineClass()方法是用來將byte字節(jié)流解析成JVM能夠識別的Class對象(ClassLoader中已實現(xiàn)該方法邏輯)蝉娜,通過這個方法不僅能夠通過class文件實例化class對象召川,也可以通過其他方式實例化class對象扮宠,如通過網(wǎng)絡接收一個類的字節(jié)碼坛增,然后轉(zhuǎn)換為byte字節(jié)流創(chuàng)建對應的Class對象,defineClass()方法通常與findClass()方法一起使用罢艾,一般情況下,在自定義類加載器時,會直接覆蓋ClassLoader的findClass()方法并編寫加載規(guī)則矫膨,取得要加載類的字節(jié)碼后轉(zhuǎn)換成流,然后調(diào)用defineClass()方法生成類的Class對象

resolveClass(Class<?> c)

使用該方法可以使用類的Class對象創(chuàng)建完成也同時被解析馁痴。前面我們說鏈接階段主要是對字節(jié)碼進行驗證,為類變量分配內(nèi)存并設置初始值同時將字節(jié)碼文件中的符號引用轉(zhuǎn)換為直接引用赠堵。

四小渊、熱部署

對于Java應用程序來說,熱部署就是在運行時更新Java類文件顾腊。

1粤铭、熱部署的原理是什么

想要知道熱部署的原理挖胃,必須要了解java類的加載過程杂靶。一個java類文件到虛擬機里的對象,要經(jīng)過如下過程酱鸭。

首先通過java編譯器吗垮,將java文件編譯成class字節(jié)碼,類加載器讀取class字節(jié)碼凹髓,再將類轉(zhuǎn)化為實例烁登,對實例newInstance就可以生成對象。

類加載器ClassLoader功能蔚舀,也就是將class字節(jié)碼轉(zhuǎn)換到類的實例饵沧。

在java應用中,所有的實例都是由類加載器赌躺,加載而來狼牺。

一般在系統(tǒng)中,類的加載都是由系統(tǒng)自帶的類加載器完成礼患,而且對于同一個全限定名的java類(如com.csiar.soc.HelloWorld)是钥,只能被加載一次掠归,而且無法被卸載。

這個時候問題就來了悄泥,如果我們希望將java類卸載虏冻,并且替換更新版本的java類,該怎么做呢弹囚?

既然在類加載器中厨相,java類只能被加載一次,并且無法卸載余寥。那是不是可以直接把類加載器給換了领铐?答案是可以的,我們可以自定義類加載器宋舷,并重寫ClassLoader的findClass方法绪撵。想要實現(xiàn)熱部署可以分以下三個步驟:

  1. 銷毀該自定義ClassLoader
  2. 更新class類文件
  3. 創(chuàng)建新的ClassLoader去加載更新后的class類文件。

2祝蝠、熱部署與熱加載

2.1音诈、Java熱部署與Java熱加載的聯(lián)系和區(qū)別

Java熱部署與熱加載的聯(lián)系

  1. 不重啟服務器編譯/部署項目
  2. 基于Java的類加載器實現(xiàn)

Java熱部署與熱加載的區(qū)別

  1. 部署方式
    • 熱部署在服務器運行時重新部署項目
    • 熱加載在運行時重新加載class
  2. 實現(xiàn)原理
    • 熱部署直接重新加載整個應用
    • 熱加載在運行時重新加載class
  3. 使用場景
    • 熱部署更多的是在生產(chǎn)環(huán)境使用
    • 熱加載則更多的實在開發(fā)環(huán)境使用

3、相關(guān)代碼

User沒有被修改類

public class User {

    public void add() {
        System.out.println("addV1,沒有修改過...");
    }
}

User更新類

public class User {

    public void add() {
        System.out.println("我把之前的user add方法修改啦!");
    }
}

自定義類加載器

public class MyClassLoader extends ClassLoader {

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            // 文件名稱
            String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
            // 獲取文件輸入流
            InputStream is = this.getClass().getResourceAsStream(fileName);
            // 讀取字節(jié)
            byte[] b = new byte[is.available()];
            is.read(b);
            // 將byte字節(jié)流解析成jvm能夠識別的Class對象
            return defineClass(name, b, 0, b.length);
        } catch (Exception e) {
            throw new ClassNotFoundException();
        }

    }

}

更新代碼

public class Hotswap {

    public static void main(String[] args)
            throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException,
            SecurityException, IllegalArgumentException, InvocationTargetException, InterruptedException {
        loadUser();
        System.gc();
        Thread.sleep(1000);// 等待資源回收
        // 需要被熱部署的class文件
        File file1 = new File("F:\\test\\User.class");
        // 之前編譯好的class文件
        File file2 = new File(
                "F:\\test\\test\\target\\classes\\com\\itmayiedu\\User.class");
        boolean isDelete = file2.delete();// 刪除舊版本的class文件
        if (!isDelete) {
            System.out.println("熱部署失敗.");
            return;
        }
        file1.renameTo(file2);
        System.out.println("update success!");
        loadUser();
    }

    public static void loadUser() throws ClassNotFoundException, InstantiationException, IllegalAccessException,
            NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException {
        MyClassLoader myLoader = new MyClassLoader();
        Class<?> class1 = myLoader.findClass("com.test.User");
        Object obj1 = class1.newInstance();
        Method method = class1.getMethod("add");
        method.invoke(obj1);
        System.out.println(obj1.getClass());
        System.out.println(obj1.getClass().getClassLoader());
    }
}

個人博客 蝸牛

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末绎狭,一起剝皮案震驚了整個濱河市细溅,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌儡嘶,老刑警劉巖喇聊,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蹦狂,居然都是意外死亡誓篱,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門凯楔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來窜骄,“玉大人,你說我怎么就攤上這事摆屯×诙簦” “怎么了?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵虐骑,是天一觀的道長准验。 經(jīng)常有香客問我,道長廷没,這世上最難降的妖魔是什么糊饱? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮腕柜,結(jié)果婚禮上济似,老公的妹妹穿的比我還像新娘矫废。我一直安慰自己,他們只是感情好砰蠢,可當我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布蓖扑。 她就那樣靜靜地躺著,像睡著了一般台舱。 火紅的嫁衣襯著肌膚如雪律杠。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天竞惋,我揣著相機與錄音柜去,去河邊找鬼。 笑死拆宛,一個胖子當著我的面吹牛嗓奢,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播浑厚,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼股耽,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了钳幅?” 一聲冷哼從身側(cè)響起物蝙,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎敢艰,沒想到半個月后诬乞,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡钠导,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年震嫉,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片辈双。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡责掏,死狀恐怖柜砾,靈堂內(nèi)的尸體忽然破棺而出湃望,到底是詐尸還是另有隱情,我是刑警寧澤痰驱,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布证芭,位于F島的核電站,受9級特大地震影響担映,放射性物質(zhì)發(fā)生泄漏废士。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一蝇完、第九天 我趴在偏房一處隱蔽的房頂上張望官硝。 院中可真熱鬧矗蕊,春花似錦、人聲如沸氢架。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽岖研。三九已至卿操,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間孙援,已是汗流浹背害淤。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留拓售,地道東北人窥摄。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像础淤,于是被迫代替她去往敵國和親溪王。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 類加載器是 Java 語言的一個創(chuàng)新值骇,也是 Java 語言流行的重要原因之一莹菱。它使得 Java 類可以被動態(tài)加載到...
    CHSmile閱讀 1,598評論 0 12
  • 目錄 一、類加載器 還記得類加載機制嗎吱瘩?類加載機制的各階段是加載道伟、連接(驗證、準備使碾、解析)蜜徽、初始化、使用票摇、卸載拘鞋。可...
    J先生有點兒屁閱讀 1,189評論 2 8
  • 本篇文章已授權(quán)微信公眾號 guolin_blog (郭霖)獨家發(fā)布 ClassLoader翻譯過來就是類加載器矢门,普...
    尼爾君閱讀 661評論 1 0
  • 轉(zhuǎn)發(fā):本篇文章已授權(quán)微信公眾號 guolin_blog (郭霖)獨家發(fā)布 ClassLoader翻譯過來就是類加載...
    尼爾君閱讀 535評論 0 1
  • 一盆色、JVM中的類加載器類型 從Java虛擬機的角度講,只有兩種不同的類加載器:啟動類加載器和其他類加載器祟剔。1.啟動...
    chenerzhu閱讀 1,981評論 0 1