成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

如何自己手寫一個(gè)熱加載

MageekChiu / 1741人閱讀

摘要:如何自己手寫一個(gè)熱加載熱加載在不停止程序運(yùn)行的情況下,對類對象的動(dòng)態(tài)替換簡述中的類從被加載到內(nèi)存中到卸載出內(nèi)存為止,一共經(jīng)歷了七個(gè)階段加載驗(yàn)證準(zhǔn)備解析初始化使用卸載。并形成一個(gè)父子結(jié)構(gòu)。

如何自己手寫一個(gè)熱加載
熱加載:在不停止程序運(yùn)行的情況下,對類(對象)的動(dòng)態(tài)替換
Java ClassLoader 簡述

Java中的類從被加載到內(nèi)存中到卸載出內(nèi)存為止,一共經(jīng)歷了七個(gè)階段:加載、驗(yàn)證、準(zhǔn)備、解析、初始化、使用、卸載。

接下來我們重點(diǎn)講解加載和初始化這兩步

加載

在加載的階段,虛擬機(jī)需要完成以下三件事:

通過一個(gè)類的全限定名來獲取定義此類的二進(jìn)制字節(jié)流

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

在內(nèi)存中生成一個(gè)代表這個(gè)類的java.lang.Class對象,作為方法區(qū)這個(gè)類的各種數(shù)據(jù)的訪問入口。

這三步都是通過類加載器來實(shí)現(xiàn)的。而官方定義的Java類加載器有BootstrapClassLoader、ExtClassLoaderAppClassLoader。這三個(gè)類加載器分別負(fù)責(zé)加載不同路徑的類的加載。并形成一個(gè)父子結(jié)構(gòu)。

類加載器名稱 負(fù)責(zé)加載目錄
BootstrapClassLoader 處于類加載器層次結(jié)構(gòu)的最高層,負(fù)責(zé) sun.boot.class.path 路徑下類的加載,默認(rèn)為 jre/lib 目錄下的核心 API 或 -Xbootclasspath 選項(xiàng)指定的 jar 包
ExtClassLoader 加載路徑為 java.ext.dirs,默認(rèn)為 jre/lib/ext 目錄或者 -Djava.ext.dirs 指定目錄下的 jar 包加載
AppClassLoader 加載路徑為 java.class.path,默認(rèn)為環(huán)境變量 CLASSPATH 中設(shè)定的值。也可以通過 -classpath 選型進(jìn)行指定
默認(rèn)情況下,例如我們使用關(guān)鍵字new或者Class.forName都是通過AppClassLoader 類加載器來加載的

正因?yàn)槭谴烁缸咏Y(jié)構(gòu),所以默認(rèn)情況下如果要加載一個(gè)類,會(huì)優(yōu)先將此類交給其父類進(jìn)行加載(直到頂層的BootstrapClassLoader 也沒有),如果父類都沒有,那么才會(huì)將此類交給子類加載。這就是類加載器的雙親委派規(guī)則。

初始化

當(dāng)我們要使用一個(gè)類的執(zhí)行方法或者屬性時(shí),類必須是加載到內(nèi)存中并且完成初始化的。那么類是什么時(shí)候被初始化的呢?有以下幾種情況

使用new關(guān)鍵字實(shí)例化對象的時(shí)候、讀取或者設(shè)置一個(gè)類的靜態(tài)字段、以及調(diào)用一個(gè)類的靜態(tài)方法。

使用java.lang.reflect包的方法對類進(jìn)行反射調(diào)用時(shí),如果類沒有進(jìn)行初始化,那么先進(jìn)行初始化。

初始化一個(gè)類的時(shí)候,如果發(fā)現(xiàn)其父類沒有進(jìn)行初始化,則先觸發(fā)父類的初始化。

當(dāng)虛擬機(jī)啟動(dòng)時(shí),用戶需要制定一個(gè)執(zhí)行的主類(包含main()方法的那個(gè)類)虛擬機(jī)會(huì)先初始化這個(gè)主類。

如何實(shí)現(xiàn)熱加載?

在上面我們知道了在默認(rèn)情況下,類加載器是遵循雙親委派規(guī)則的。所以我們要實(shí)現(xiàn)熱加載,那么我們需要加載的那些類就不能交給系統(tǒng)加載器來完成。所以我們要自定義類加載器來寫我們自己的規(guī)則。

實(shí)現(xiàn)自己的類加載器

要想實(shí)現(xiàn)自己的類加載器,只需要繼承ClassLoader類即可。而我們要打破雙親委派規(guī)則,那么我們就必須要重寫loadClass方法,因?yàn)槟J(rèn)情況下loadClass方法是遵循雙親委派的規(guī)則的。

public class CustomClassLoader extends ClassLoader{

    private static final String CLASS_FILE_SUFFIX = ".class";

    //AppClassLoader的父類加載器
    private ClassLoader extClassLoader;

    public CustomClassLoader(){
        ClassLoader j = String.class.getClassLoader();
        if (j == null) {
            j = getSystemClassLoader();
            while (j.getParent() != null) {
                j = j.getParent();
            }
        }
        this.extClassLoader = j ;
    }

    protected Class loadClass(String name, boolean resolve){

        Class cls = null;
        cls = findLoadedClass(name);
        if (cls != null){
            return cls;
        }
        //獲取ExtClassLoader
        ClassLoader extClassLoader = getExtClassLoader() ;
        //確保自定義的類不會(huì)覆蓋Java的核心類
        try {
            cls = extClassLoader.loadClass(name);
            if (cls != null){
                return cls;
            }
        }catch (ClassNotFoundException e ){

        }
        cls = findClass(name);
        return cls;
    }

    @Override
    public Class findClass(String name) {
        byte[] bt = loadClassData(name);
        return defineClass(name, bt, 0, bt.length);
    }

    private byte[] loadClassData(String className) {
        // 讀取Class文件呢
        InputStream is = getClass().getClassLoader().getResourceAsStream(className.replace(".", "/")+CLASS_FILE_SUFFIX);
        ByteArrayOutputStream byteSt = new ByteArrayOutputStream();
        // 寫入byteStream
        int len =0;
        try {
            while((len=is.read())!=-1){
                byteSt.write(len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 轉(zhuǎn)換為數(shù)組
        return byteSt.toByteArray();
    }

    public ClassLoader getExtClassLoader(){
        return extClassLoader;
    }
}

為什么要先獲取ExtClassLoader類加載器呢?其實(shí)這里是借鑒了Tomcat里面的設(shè)計(jì),是為了避免我們自定義的類加載器覆蓋了一些核心類。例如java.lang.Object。

為什么是獲取ExtClassLoader 類加載器而不是獲取AppClassLoader 呢?這是因?yàn)槿绻覀儷@取了AppClassLoader 進(jìn)行加載,那么不還是雙親委派的規(guī)則了嗎?

監(jiān)控class文件

這里我們使用ScheduledThreadPoolExecutor 來進(jìn)行周期性的監(jiān)控文件是否修改。在程序啟動(dòng)的時(shí)候記錄文件的最后修改時(shí)間。隨后周期性的查看文件的最后修改時(shí)間是否改動(dòng)。如果改動(dòng)了那么就重新生成類加載器進(jìn)行替換。這樣新的文件就被加載進(jìn)內(nèi)存中了。

首先我們建立一個(gè)需要監(jiān)控的文件

public class Test {

    public void test(){
        System.out.println("Hello World! Version one");
    }
}

我們通過在程序運(yùn)行時(shí)修改版本號,來動(dòng)態(tài)的輸出版本號。接下來我們建立周期性執(zhí)行的任務(wù)類。

public class WatchDog implements Runnable{

    private Map fileDefineMap;

    public WatchDog(Map fileDefineMap){
        this.fileDefineMap = fileDefineMap;
    }

    @Override
    public void run() {
        File file = new File(FileDefine.WATCH_PACKAGE);
        File[] files = file.listFiles();
        for (File watchFile : files){
            long newTime = watchFile.lastModified();
            FileDefine fileDefine = fileDefineMap.get(watchFile.getName());
            long oldTime = fileDefine.getLastDefine();
            //如果文件被修改了,那么重新生成累加載器加載新文件
            if (newTime!=oldTime){
                fileDefine.setLastDefine(newTime);
                loadMyClass();
            }
        }
    }

    public void loadMyClass(){
        try {
            CustomClassLoader customClassLoader = new CustomClassLoader();
            Class cls = customClassLoader.loadClass("com.example.watchfile.Test",false);
            Object test = cls.newInstance();
            Method method = cls.getMethod("test");
            method.invoke(test);
        }catch (Exception e){
            System.out.println(e);
        }
    }
}

可以看到在上面的gif演示圖中我們簡單的實(shí)現(xiàn)了熱加載的功能。

優(yōu)化

在上面的方法調(diào)用中我們是使用了getMethod()方法來調(diào)用的。此時(shí)或許會(huì)有疑問,為什么不直接將newInstance()強(qiáng)轉(zhuǎn)為Test類呢?

如果我們使用了強(qiáng)轉(zhuǎn)的話,代碼會(huì)變成這樣Test test = (Test) cls.newInstance()。但是在運(yùn)行的時(shí)候會(huì)拋ClassCastException 異常。這是為什么呢?因?yàn)樵贘ava中確定兩個(gè)類是否相等,除了看他們兩個(gè)類文件是否相同以外還會(huì)看他們的類加載器是否相同。所以即使是同一個(gè)類文件,如果是兩個(gè)不同的類加載器來加載的,那么它們的類型就是不同的。

WatchDog類是由我們new出來的。所以默認(rèn)是AppClassLoader 來加載的。所以test 變量的聲明類型是WatchDog 方法中的一個(gè)屬性,所以也是由AppClassLoader 來加載的。因此兩個(gè)類不相同。

該如何解決呢?問題就出在了=號雙方的類不一樣,那么我們給它搞成一樣不就行了嗎?怎么搞?答案就是接口。默認(rèn)情況下,如果我們實(shí)現(xiàn)了一個(gè)接口,那么此接口一般都是以子類的加載器為主的。意思就是如果沒有特殊要求的話,例如A implements B 如果A的加載器是自定義的。那么B接口的加載器也是和子類是一樣的。

所以我們要將接口的類加載器搞成是AppClassLoader 來加載。所以自定義加載器中加入這一句

if ("com.example.watchfile.ITest".equals(name)){
    try {
        cls = getSystemClassLoader().loadClass(name);
    } catch (ClassNotFoundException e) {

    }
    return cls;
}

建立接口

public interface ITest {

    void test();
}

這樣我們就能愉快的調(diào)用了。直接調(diào)用其方法。不會(huì)拋異常,因?yàn)?b>=號雙方的類是一樣的。

CustomClassLoader customClassLoader = new CustomClassLoader();
Class cls = customClassLoader.loadClass("com.example.watchfile.Test",false);
ITest test = (ITest) cls.newInstance();
test.test();
源代碼地址Github 參考文章

https://www.ibm.com/developerworks/cn/java/j-lo-hotswapcls/index.html

[Java虛擬機(jī)]()

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/75370.html

相關(guān)文章

  • 9102年:手寫一個(gè)React腳手架 【優(yōu)化極致版】

    摘要:馬上要出了,完全手寫一個(gè)優(yōu)化后的腳手架是不可或缺的技能。每個(gè)依賴項(xiàng)隨即被處理,最后輸出到稱之為的文件中,我們將在下一章節(jié)詳細(xì)討論這個(gè)過程。的事件流機(jī)制保證了插件的有序性,使得整個(gè)系統(tǒng)擴(kuò)展性很好。 webpack馬上要出5了,完全手寫一個(gè)優(yōu)化后的腳手架是不可或缺的技能。 本文書寫時(shí)間 2019年5月9日 , webpack版本 4.30.0最新版本 本人所有代碼均手寫,親自試驗(yàn)過可...

    Kylin_Mountain 評論0 收藏0
  • 9102年:手寫一個(gè)React腳手架 【優(yōu)化極致版】

    摘要:馬上要出了,完全手寫一個(gè)優(yōu)化后的腳手架是不可或缺的技能。每個(gè)依賴項(xiàng)隨即被處理,最后輸出到稱之為的文件中,我們將在下一章節(jié)詳細(xì)討論這個(gè)過程。的事件流機(jī)制保證了插件的有序性,使得整個(gè)系統(tǒng)擴(kuò)展性很好。 webpack馬上要出5了,完全手寫一個(gè)優(yōu)化后的腳手架是不可或缺的技能。 本文書寫時(shí)間 2019年5月9日 , webpack版本 4.30.0最新版本 本人所有代碼均手寫,親自試驗(yàn)過可...

    whatsns 評論0 收藏0
  • 9102年:手寫一個(gè)React腳手架 【優(yōu)化極致版】

    摘要:馬上要出了,完全手寫一個(gè)優(yōu)化后的腳手架是不可或缺的技能。每個(gè)依賴項(xiàng)隨即被處理,最后輸出到稱之為的文件中,我們將在下一章節(jié)詳細(xì)討論這個(gè)過程。的事件流機(jī)制保證了插件的有序性,使得整個(gè)系統(tǒng)擴(kuò)展性很好。 webpack馬上要出5了,完全手寫一個(gè)優(yōu)化后的腳手架是不可或缺的技能。 本文書寫時(shí)間 2019年5月9日 , webpack版本 4.30.0最新版本 本人所有代碼均手寫,親自試驗(yàn)過可...

    bingo 評論0 收藏0
  • 史上最全 Android 中高級工程師面試復(fù)習(xí)大綱

    摘要:找工作之前看了很多面試題,復(fù)習(xí)資料,但是發(fā)現(xiàn)純看面試題是不行的,因?yàn)榭勘车臇|西是記不牢的,需要知識成體系才可以,所以筆者整理了一份復(fù)習(xí)大綱,基本涵蓋了中高級工程師面試所必須知識點(diǎn),希望可以通過此文幫助一些想換工作的朋友更好的復(fù)習(xí),準(zhǔn)備面試。 概述 都說金三銀四青銅五,這幾個(gè)月份是程序員最好的跳槽時(shí)間,筆者四月初也換了工作。找工作之前看了很多面試題,復(fù)習(xí)資料,但是發(fā)現(xiàn)純看面試題是不行的,因?yàn)榭?..

    chengjianhua 評論0 收藏0

發(fā)表評論

0條評論

最新活動(dòng)
閱讀需要支付1元查看
<