1. 程式人生 > 其它 >《深入理解 Java 虛擬機器》閱讀筆記2系列:附錄:實現Java類的熱替換

《深入理解 Java 虛擬機器》閱讀筆記2系列:附錄:實現Java類的熱替換

技術標籤:Javajavajvmjdk

實現 Java 類的熱替換

什麼是熱替換及其實現原理

  • 熱替換是在不停止正在執行的系統的情況下進行類(物件)的升級替換;
  • 這要求虛擬機器中要存在同一個類的兩個不同版本。可我們知道,我們是無法將同一個類載入兩遍的,想要實現這點,我們需要讓虛擬機器認為這是兩個不同的類,即用兩個不同的類載入器去載入這個類不同版本的 class 檔案;
  • 因此,這個工作就不能由系統提供給我們的啟動類載入器,擴充套件類載入器或者應用程式類載入器來完成,因為這三個類載入器在同一個虛擬機器中只有一份,不僅如此,我們還要跳過這些類載入器;
  • 想要跳過這些類載入器可不是隻要不用這些類載入器就行了,還需要我們跳過雙親委派模型,否則類的載入還會被委派到這些個類載入器,如果恰好某個類之前是由這三個類載入器中的一個載入的,虛擬機器就不會再次載入新版本的類了,就無法實現類的熱替換了。

實現簡單的 Java 類熱替換

需求分析

現有一 Foo 類,可以在控制檯持續列印:Hello world! version one,我們將在該類執行時,將其 .class 檔案替換為修改後的 Foo 類的 .class 檔案,修改後的 Foo 會在控制檯持續列印:Hello world! version two。也就是說,替換之後,控制檯列印的內容發生變化,就說明類的熱替換實現成功。

Foo 類的實現:

public class Foo {
    public void sayHello() {
        System.out.println("Hello world! version one");
        // System.out.println("Hello world! version two");  // 之後替換成這個
    }
}

然後我們通過如下程式執行 Foo 類:

public class Task extends TimerTask {
    @Override
    public void run
() { String basePath = "C:\\Users\\Bean\\IdeaProjects\\USTJ\\target\\classes"; // 每執行一次任務都 new 一個新的類載入器 HotswapClassLoader cl = new HotswapClassLoader( basePath, new String[]{"com.jvm.ch7.hotswap.Foo"}); try { // 通過我們自己實現的類載入器載入 Foo 類 Class cls = cl.loadClass("com.jvm.ch7.hotswap.Foo", true); Object foo = cls.newInstance(); Method method = cls.getMethod("sayHello", new Class[]{}); method.invoke(foo, new Object[]{}); } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) { Timer timer = new Timer(); timer.schedule(new Task(), 0, 1000); } }

實現類載入器

HotswapClassLoader 的實現如下,具體的講解已被寫入註釋中:

public class HotswapClassLoader extends ClassLoader {
    private String basePath;
    private HashSet<String> loadedClass;  // 用來記錄被這個類載入器所載入的類

    public HotswapClassLoader(String basePath, String[] classList) {
        // 跳過父類載入器,把它設為null
        super(null);
        this.basePath = basePath;
        loadedClass = new HashSet<>();
        // 該類載入器在初始化的時候會直接把應該它負責載入的類載入好,
        // 這樣之後 loadClass 時,會在第一步檢驗該類是否已經被載入時發現該類已經被載入過了,
        // 就無需執行 loadClass 之後的流程,直接返回虛擬機器中被載入好的類即可,
        // 這樣雖然初始化的時間長了點,但是之後 loadClass 時會比較省時間
        loadClassByMe(classList);
    }

    /**
     * 載入給定的的 classList 中的類到虛擬機器
     */
    private void loadClassByMe(String[] classList) {
        for (int i = 0; i < classList.length; i++) {
            Class cls = loadClassDirectly(classList[i]);
            if (cls != null) {
                loadedClass.add(classList[i]);
            }
        }
    }

    /**
     * 通過檔名直接載入類,得到Class物件
     */
    private Class loadClassDirectly(String className) {
        Class cls = null;
        StringBuilder sb = new StringBuilder(basePath);
        String classPath = className.replace(".", File.separator) + ".class";
        sb.append(File.separator + classPath);
        File file = new File(sb.toString());
        InputStream fin = null;
        try {
            fin = new FileInputStream(file);
            // 將位元組流轉化成記憶體中的Class物件
            cls = instantiateClass(className, fin, (int) file.length());
            return cls;
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fin != null) {
                try {
                    fin.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }

    /**
     * 將位元組流轉化成記憶體中的Class物件啊,使用defineClass方法!
     */
    private Class instantiateClass(String name, InputStream fin, int len) {
        byte[] buffer = new byte[len];
        try {
            fin.read(buffer);
            return defineClass(name, buffer, 0, len);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fin != null) {
                try {
                    fin.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }

    /**
     * 覆蓋原有的loadClass規則,
     */
    public Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
        Class cls = null;
        // 應該由 HotswapClassLoader 負責載入的類會通過下面這一行得到類的 Class 物件,
        // 因為早在 HotswapClassLoader 類載入器執行建構函式時,它們就被載入好了
        cls = findLoadedClass(name);
        // 只有在這個類沒有被載入,且!這個類不是當前這個類載入器負責載入的時候,才去使用啟動類載入器
        if (cls == null && !loadedClass.contains(name)) {
            cls = findSystemClass(name);
        }
        if (cls == null) {
            throw new ClassNotFoundException(name);
        }
        // resolveClass是進行連線操作的,即"驗證+準備+解析",之後就可以進行初始化了
        if (resolve) {
            resolveClass(cls);
        }
        return cls;
    }
}