程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> Java類加載機制,java加載機制

Java類加載機制,java加載機制

編輯:JAVA綜合教程

Java類加載機制,java加載機制


什麼是類加載器

負責讀取 Java 字節代碼,並轉換成java.lang.Class類的一個實例;

類加載器與類的”相同“判斷

類加載器除了用於加載類外,還可用於確定類在Java虛擬機中的唯一性。

即便是同樣的字節代碼,被不同的類加載器加載之後所得到的類,也是不同的。

通俗一點來講,要判斷兩個類是否“相同”,前提是這兩個類必須被同一個類加載器加載,否則這個兩個類不“相同”。
這裡指的“相同”,包括類的Class對象的equals()方法、isAssignableFrom()方法、isInstance()方法、instanceof關鍵字等判斷出來的結果。

類加載器種類

啟動類加載器,Bootstrap ClassLoader,加載JACA_HOME\lib,或者被-Xbootclasspath參數限定的類
擴展類加載器,Extension ClassLoader,加載\lib\ext,或者被java.ext.dirs系統變量指定的類
應用程序類加載器,Application ClassLoader,加載ClassPath中的類庫
自定義類加載器,通過繼承ClassLoader實現,一般是加載我們的自定義類

雙親委派模型

類加載器 Java 類如同其它的 Java 類一樣,也是要由類加載器來加載的;除了啟動類加載器,每個類都有其父類加載器(父子關系由組合(不是繼承)來實現);

所謂雙親委派是指每次收到類加載請求時,先將請求委派給父類加載器完成(所有加載請求最終會委派到頂層的Bootstrap ClassLoader加載器中),如果父類加載器無法完成這個加載(該加載器的搜索范圍中沒有找到對應的類),子類嘗試自己加載。


雙親委派好處

  • 避免同一個類被多次加載;
  • 每個加載器只能加載自己范圍內的類;

類加載過程

類加載分為三個步驟:加載連接初始化

如下圖 , 是一個類從加載到使用及卸載的全部生命周期,圖片來自參考資料;

加載

根據一個類的全限定名(如cn.edu.hdu.test.HelloWorld.class)來讀取此類的二進制字節流到JVM內部;

將字節流所代表的靜態存儲結構轉換為方法區的運行時數據結構(hotspot選擇將Class對象存儲在方法區中,Java虛擬機規范並沒有明確要求一定要存儲在方法區或堆區中)

轉換為一個與目標類型對應的java.lang.Class對象;

連接

驗證

驗證階段主要包括四個檢驗過程:文件格式驗證、元數據驗證、字節碼驗證和符號引用驗證;

准備

為類中的所有靜態變量分配內存空間,並為其設置一個初始值(由於還沒有產生對象,實例變量將不再此操作范圍內);

解析

將常量池中所有的符號引用轉為直接引用(得到類或者字段、方法在內存中的指針或者偏移量,以便直接調用該方法)。這個階段可以在初始化之後再執行。

初始化

  • 在連接的准備階段,類變量已賦過一次系統要求的初始值,而在初始化階段,則是根據程序員自己寫的邏輯去初始化類變量和其他資源,舉個例子如下:
    public static int value1  = 5;
    public static int value2  = 6;
    static{
        value2 = 66;
    }

在准備階段value1和value2都等於0;

在初始化階段value1和value2分別等於5和66;

  • 所有類變量初始化語句和靜態代碼塊都會在編譯時被前端編譯器放在收集器裡頭,存放到一個特殊的方法中,這個方法就是<clinit>方法,即類/接口初始化方法,該方法只能在類加載的過程中由JVM調用;
  • 編譯器收集的順序是由語句在源文件中出現的順序所決定的,靜態語句塊中只能訪問到定義在靜態語句塊之前的變量;
  • 如果超類還沒有被初始化,那麼優先對超類初始化,但在<clinit>方法內部不會顯示調用超類的<clinit>方法,由JVM負責保證一個類的<clinit>方法執行之前,它的超類<clinit>方法已經被執行。
  • JVM必須確保一個類在初始化的過程中,如果是多線程需要同時初始化它,僅僅只能允許其中一個線程對其執行初始化操作,其余線程必須等待,只有在活動線程執行完對類的初始化操作之後,才會通知正在等待的其他線程。(所以可以利用靜態內部類實現線程安全的單例模式)
  • 如果一個類沒有聲明任何的類變量,也沒有靜態代碼塊,那麼可以沒有類<clinit>方法;

何時觸發初始化

 自定義類加載器

 要創建用戶自己的類加載器,只需要繼承java.lang.ClassLoader類,然後覆蓋它的findClass(String name)方法即可,即指明如何獲取類的字節碼流。

如果要符合雙親委派規范,則重寫findClass方法(用戶自定義類加載邏輯);要破壞的話,重寫loadClass方法(雙親委派的具體邏輯實現)

例子:

package classloader;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

class TestClassLoad {
    @Override
    public String toString() {
        return "類加載成功。";
    }
}
public class PathClassLoader extends ClassLoader {
    private String classPath;

    public PathClassLoader(String classPath) {
        this.classPath = classPath;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] classData = getData(name);
        if (classData == null) {
            throw new ClassNotFoundException();
        } else {
            return defineClass(name, classData, 0, classData.length);
        }
    }

    private byte[] getData(String className) {
        String path = classPath + File.separatorChar
                + className.replace('.', File.separatorChar) + ".class";
        try {
            InputStream is = new FileInputStream(path);
            ByteArrayOutputStream stream = new ByteArrayOutputStream();
            byte[] buffer = new byte[2048];
            int num = 0;
            while ((num = is.read(buffer)) != -1) {
                stream.write(buffer, 0, num);
            }
            return stream.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return null;
    }



    public static void main(String args[]) throws ClassNotFoundException,
            InstantiationException, IllegalAccessException {
        ClassLoader pcl = new PathClassLoader("D:\\ProgramFiles\\eclipseNew\\workspace\\cp-lib\\bin");
        Class c = pcl.loadClass("classloader.TestClassLoad");//注意要包括包名
        System.out.println(c.newInstance());//打印類加載成功.
    }
}

JAVA熱部署實現

首先談一下何為熱部署(hotswap),熱部署是在不重啟 Java 虛擬機的前提下,能自動偵測到 class 文件的變化,更新運行時 class 的行為。Java 類是通過 Java 虛擬機加載的,某個類的 class 文件在被 classloader 加載後,會生成對應的 Class 對象,之後就可以創建該類的實例。默認的虛擬機行為只會在啟動時加載類,如果後期有一個類需要更新的話,單純替換編譯的 class 文件,Java 虛擬機是不會更新正在運行的 class。如果要實現熱部署,最根本的方式是修改虛擬機的源代碼,改變 classloader 的加載行為,使虛擬機能監聽 class 文件的更新,重新加載 class 文件,這樣的行為破壞性很大,為後續的 JVM 升級埋下了一個大坑。

另一種友好的方法是創建自己的 classloader 來加載需要監聽的 class,這樣就能控制類加載的時機,從而實現熱部署。

 

以上內容主要整理自網絡,加入若干個人理解。

參考資料:

https://www.ibm.com/developerworks/cn/java/j-lo-hotdeploy/

https://askingwindy.gitbooks.io/gitbook-java-interview-note/content/jvm/classloader/classloader.html

https://segmentfault.com/a/1190000004597758

https://www.ibm.com/developerworks/cn/java/j-lo-classloader/

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved