類加載機制之ClassLoader

1,類加載java

每一個編寫的」.java」拓展名類文件都存儲着須要執行的程序邏輯,這些」.java」文件通過Java編譯器編譯成拓展名爲」.class」的文件,」.class」文件中保存着Java代碼經轉換後的虛擬機指令緩存

當須要使用某個類時,虛擬機將會加載它的」.class」文件,並建立對應的class對象,將class文件加載到虛擬機的內存,這個過程稱爲類加載,這裏咱們須要瞭解一下類加載的過程,以下:安全

建立的對象存儲在java 堆內存,對象的引用存儲在java 虛擬機棧  class 對象中包含的方法,屬性存儲在方法區(JDK1.8 以前叫叫作永久區)服務器

Jvm執行class文件網絡

Loading:數據結構

step1: 類加載器將.class文件的二進制數據從外部存儲器(如光盤,硬盤)調入內存中,在方法區生成方法區中的運行數據,生成java.lang.Class 對象(存儲在java 堆)jvm

step2: CPU再從內存中讀取指令和數據進行運算,並將運算結果存入內存中(方法的返回結果也是存在方法區中) 。直接給CPU處理,而因爲CPU的處理速度遠遠大於調入數據的速度,容易形成數據的脫節,因此須要內存起緩衝做用ide

每一個類都對應有一個Class類型的對象,Class類的構造方法是私有的,只有JVM可以建立。所以Class對象是反射的入口,使用該對象就能夠得到目標類所關聯的.class文件中具體的數據結構。函數

Linking:測試

驗證:確保加載的類信息符合JVM規範,沒有安全方面的問題
準備:正式爲類變量(static變量)分配內存並設置類變量初始值的階段,這些內存都將在方法區中進行分配
解析:虛擬機常量池的符號引用替換爲字節引用過程

initilization:初始化

當初始化一個類的時候,若是發現其父類尚未進行過初始化,則須要先觸發其父類的初始化

初始化過程:

類構造器<clinit>()方法是由編譯器自動收藏類中的全部類變量的賦值動做和靜態語句塊(static塊)中的語句合併產生,代碼從上往下執行。

也就是說這個方法,在類初始化階段,會將類中全部的靜態變量收集到一塊而且分配空間,順序是從上往下執行,若一個靜態變量屢次賦值,靜態函數裏面若沒有類型也是能夠的,會默認初始化:

static {
        agent = 0;
    }

private static int agent = 9;

2,類加載器經常使用方法
loadClass(String)
該方法加載指定名稱(包括包名)的二進制類型,該方法在JDK1.2以後再也不建議用戶重寫但用戶能夠直接調用該方法,loadClass()方法是ClassLoader類本身實現的,該方法中的邏輯就是雙親委派模式的實現,其源碼以下,loadClass(String name, boolean resolve)是一個重載方法,resolve參數表明是否生成class對象的同時進行解析相關操做。
正如loadClass方法所展現的,當類加載請求到來時,先從緩存中查找該類對象,若是存在直接返回,若是不存在則交給該類加載去的父加載器去加載,假若沒有父加載則交給頂級啓動類加載器去加載,最後假若仍沒有找到,則使用findClass()方法去加載(關於findClass()稍後會進一步介紹)。從loadClass實現也能夠知道若是不想從新定義加載類的規則,也沒有複雜的邏輯,只想在運行時加載本身指定的類,那麼咱們能夠直接使用this.getClass().getClassLoder.loadClass("className"),這樣就能夠直接調用ClassLoader的loadClass方法獲取到class對象。
findClass(String)
在JDK1.2以前,在自定義類加載時,總會去繼承ClassLoader類並重寫loadClass方法,從而實現自定義的類加載類,可是在JDK1.2以後已再也不建議用戶去覆蓋loadClass()方法,而是建議把自定義的類加載邏輯寫在findClass()方法中,從前面的分析可知,findClass()方法是在loadClass()方法中被調用的,當loadClass()方法中父加載器加載失敗後,則會調用本身的findClass()方法來完成類加載,這樣就能夠保證自定義的類加載器也符合雙親委託模式。須要注意的是ClassLoader類中並無實現findClass()方法的具體代碼邏輯,取而代之的是拋出ClassNotFoundException異常,同時應該知道的是findClass方法一般是和defineClass方法一塊兒使用的(稍後會分析)
defineClass(byte[] b, int off, int len)
defineClass()方法是用來將byte字節流解析成JVM可以識別的Class對象(ClassLoader中已實現該方法邏輯),經過這個方法不只可以經過class文件實例化class對象,也能夠經過其餘方式實例化class對象,如經過網絡接收一個類的字節碼,而後轉換爲byte字節流建立對應的Class對象,defineClass()方法一般與findClass()方法一塊兒使用,通常狀況下,在自定義類加載器時,會直接覆蓋ClassLoader的findClass()方法並編寫加載規則,取得要加載類的字節碼後轉換成流,而後調用defineClass()方法生成類的Class對象
resolveClass(Class≺?≻ c)
使用該方法可使用類的Class對象建立完成也同時被解析。前面咱們說連接階段主要是對字節碼進行驗證,爲類變量分配內存並設置初始值同時將字節碼文件中的符號引用轉換爲直接引用。

3,熱部署

對於Java應用程序來講,熱部署就是在運行時更新Java類文件。

通俗的說,項目在運行過程當中,已經部署到服務器的項目,發現邏輯有問題,須要修改代碼,可是又不想在修改完畢以後從新部署,這時候就須要熱部署的方法。

熱部署就是直接修改.class 文件,可是修改以後,並不會生效,由於以前版本的.class 文件已經在項目啓動的時候被類加載器讀取到內存中,並且這個過程只會發生一次(除非從新部署),因此咱們要實現熱部署須要作的就是:

1,銷燬以前的ClassLoader加載的對象,讓System.gc(),提醒垃圾回收

2,更新字節碼文件

3,建立新的自定義ClassLoader 讀取字節碼文件

 

Java熱部署與熱加載的聯繫

1.不重啓服務器編譯/部署項目

2.基於Java的類加載器實現

Java熱部署與熱加載的區別

部署方式

熱部署在服務器運行時從新部署項目

熱加載在運行時從新加載class

實現原理

熱部署直接從新加載整個應用

熱加載在運行時從新加載class

使用場景

熱部署更多的是在生產環境使用

熱加載則更多的實在開發環境使用

4,例子:

建立一個對象HelloWorld 類,裏面打印Hello World 1,用記事本建立和HelloWorld類同樣的java 文件,打印的是Hello World 2,Hello World2 的類用javac 編譯,生成.class文件,在項目運行中先加載以前的class 文件,再用自定義的classLoader自動的去更新字節碼文件,查看輸出是否有變化。

 

 

package com.hella.hotswap;

public class HelloWorld {
    
    public void search(){
        System.out.println("Hello World 1");
    }

}

 

建立對象:

package com.hella.hotswap;

public class HelloWorld {
    
    public void search(){
        System.out.println("Hello World 2");
    }

}

 

//自定義ClassLoader

public class ClassLoaderDemo extends ClassLoader {

    // name 是一個全路徑 如 com.baidu.dev.HelloWorld
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            // 文件名稱
            String fileName = "/"+ name.replace(".", "/")+".class";
            // 獲取文件輸入流
            InputStream is = this.getClass().getResourceAsStream(fileName); //加 / 表明從classes 的根目錄下開始找 // 讀取字節
            byte[] b = new byte[is.available()];
            is.read(b);
            // 將byte字節流解析成jvm可以識別的Class對象
            return defineClass(name, b, 0, b.length);
        } catch (IOException e) {
            throw new ClassNotFoundException();
        }
    }

}

測試:

import java.io.File;
import java.lang.reflect.Method;

public class App {
        public static void main(String[] args) throws Exception {
            loadObject();
            System.gc();
            Thread.sleep(1000);// 等待資源回收
            // 須要被熱部署的class文件
            File file1 = new File("C:\\Users\\caich5\\Desktop\\test\\HelloWorld.class");
            // 以前編譯好的class文件
            File file2 = new File(
                    "C:\\Users\\caich5\\workspace\\JvmWeb\\target\\classes\\com\\hella\\hotswap\\HelloWorld.class");
            boolean isDelete = file2.delete();// 刪除舊版本的class文件
            if (!isDelete) {
                System.out.println("熱部署失敗.");
                return;
            }
            file1.renameTo(file2);
            System.out.println("update success!");
            loadObject();
            
            
        }

        private static void loadObject() throws  Exception {
            ClassLoaderDemo classLoaderDemo = new ClassLoaderDemo();
            Class<?> clazz = classLoaderDemo.findClass("com.hella.hotswap.HelloWorld");
            Object object = clazz.newInstance();        
            //經過反射機制獲取方法
            Method method = clazz.getMethod("search");
            //反射機制對象執行方法
            method.invoke(object);
            
            
        }
    }

打印:

     Hello World 1     update success!     Hello World 2

相關文章
相關標籤/搜索