類裝載器

1. class裝載驗(yàn)證流程

1.1. 加載 裝載類的第一個(gè)階段, 取得類的二進(jìn)制流,轉(zhuǎn)為方法區(qū)數(shù)據(jù)結(jié)構(gòu),在Java堆中生成對(duì)應(yīng)的java.lang.Class對(duì)象
1.2. 鏈接
  • 1.2.1 驗(yàn)證 保證Class流的格式是正確的

    • 文件格式驗(yàn)證
      • 是否以0xCAFEBABE開(kāi)頭
      • 版本號(hào)是否合理
    • 1.2.2 元數(shù)據(jù)驗(yàn)證
      • 是否有父類
      • 繼承了final類?
      • 非抽象類實(shí)現(xiàn)了所有的抽象方法
    • 1.2.3 字節(jié)碼驗(yàn)證 (很復(fù)雜) 通過(guò)驗(yàn)證的class也不一定是沒(méi)有問(wèn)題的
      • 運(yùn)行檢查
      • 棧數(shù)據(jù)類型和操作碼數(shù)據(jù)參數(shù)吻合
      • 跳轉(zhuǎn)指令指定到合理的位置
    • 1.2.4 符號(hào)引用驗(yàn)證
      • 常量池中描述類是否存在
      • 訪問(wèn)的方法或字段是否存在且有足夠的權(quán)限
  • 準(zhǔn)備

    • 分配內(nèi)存,并為類設(shè)置初始值 (方法區(qū)中)
      • public static int v=1;
      • 在準(zhǔn)備階段中,v會(huì)被設(shè)置為0
      • 在初始化的<clinit>中才會(huì)被設(shè)置為1
      • 對(duì)于static final類型,在準(zhǔn)備階段就會(huì)被賦上正確的值(常量在準(zhǔn)備時(shí)就會(huì)被賦上正確的值)
        public static final int v=1;
  • 解析

    • 符號(hào)引用 字符串引用對(duì)象不一定被加載替換為直接引用指針或者地址偏移量引用對(duì)象一定在內(nèi)存
1.3. 初始化
  • 執(zhí)行類構(gòu)造器<clinit>
    • static變量 賦值語(yǔ)句
    • static{}語(yǔ)句
  • 子類的<clinit>調(diào)用前保證父類的<clinit>被調(diào)用
  • <clinit>是線程安全的
  • 思考:Java.lang.NoSuchFieldError錯(cuò)誤可能在什么階段拋出

什么是類裝載器

  • ClassLoader是一個(gè)抽象類
  • ClassLoader的實(shí)例將讀入Java字節(jié)碼將類裝載到JVM中
  • ClassLoader可以定制,滿足不同的字節(jié)碼流獲取方式
  • ClassLoader負(fù)責(zé)類裝載過(guò)程中的加載階段

JDK中ClassLoader默認(rèn)設(shè)計(jì)模式

  • 重要方法
public Class<?> loadClass(String name) throws ClassNotFoundException
載入并返回一個(gè)Class
protected final Class<?> defineClass(byte[] b, int off, int len)
定義一個(gè)類,不公開(kāi)調(diào)用
protected Class<?> findClass(String name) throws ClassNotFoundException
loadClass回調(diào)該方法,自定義ClassLoader的推薦做法
protected final Class<?> findLoadedClass(String name) 
尋找已經(jīng)加載的類(找不到才要去加載類)
  • 分類
    BootStrap ClassLoader 啟動(dòng)類加載器
    Extension ClassLoader擴(kuò)展類加載器
    App ClassLoader應(yīng)用/系統(tǒng)類加載器
    Custom ClassLoader自定義類加載器
    啟動(dòng)類加載器沒(méi)有parent,因?yàn)樗亲铐攲?,其他每個(gè)ClassLoader都有一個(gè)Parent作為父親
    參考
    自底向上:檢查類是否已被加載
    自頂向下:嘗試加載類
類加載器關(guān)系.png

-Xbootclasspath 可以手動(dòng)指定 boot classpath
類加載例子:
位于:D:\helloJVM\HelloClassLoader.java:

public class HelloClassLoader {
    public void print(){
        System.out.println("I am in bootloader");
    }
}

應(yīng)用中的HelloClassLoader.java:

public class HelloClassLoader {
    public void print(){
        System.out.println("I am in apploader");
    }
}

及定位class是被哪個(gè)loader定位的應(yīng)用程序:

public class LookForClassLoader {
  public static void main(String[] args) {
      HelloClassLoader loader=new HelloClassLoader();
      loader.print();
  }
  1. 直接執(zhí)行main方法(即用app loader來(lái)加載 HelloClassLoader )


    loader.print()結(jié)果.png
  2. 添加參數(shù)-Xbootclasspath/a:D:\helloJVM,并編譯HelloClassLoader.java。執(zhí)行main方法

D:\>cd helloJVM
D:\helloJVM>javac HelloClassLoader.java
要記得編譯哦.png
loader.print()結(jié)果
  • I am in apploader 在classpath中卻沒(méi)有被加載,說(shuō)明類的加載是從上往下
  1. 保持2的條件不變,強(qiáng)制在app 類加載器中加載classPath中的 HelloClassLoader
public class LookForClassLoader {
    public static void main(String[] args) {
        try {
            forceLoadInAppLoader();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void forceLoadInAppLoader() throws Exception {
        //  強(qiáng)制在apploader中加載 HelloClassLoader
        ClassLoader cl = LookForClassLoader.class.getClassLoader();
        byte[] bHelloClassLoader = loadClassBytes("jvmstudy.HelloClassLoader");
        Method md_defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
        md_defineClass.setAccessible(true);
        md_defineClass.invoke(cl, bHelloClassLoader, 0, bHelloClassLoader.length);
        md_defineClass.setAccessible(false);
        HelloClassLoader loader = new HelloClassLoader();
        System.out.println(loader.getClass().getClassLoader());
        loader.print();
    }


    private static byte[] loadClassBytes(String className) throws IOException {
        //獲取class文件路徑
        String classFile = getClassFile(className);
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(classFile);
        } catch (FileNotFoundException e) {
            System.out.println(e);
            return null;
        }
        byte[] bytes = new byte[fis.available()];
        fis.read(bytes);
        fis.close();
        return bytes;
    }

    private static String getClassFile(String name) {
        StringBuffer sb = new StringBuffer("E:\\myspace\\local-git-hub\\Steping\\src\\");
        name = name.replace('.', File.separatorChar) + ".class";
        sb.append(File.separatorChar + name);
        return sb.toString();
    }
}
loader.print()結(jié)果.png
  • 在查找類的時(shí)候,先在底層的Loader查找,是從下往上的。app Loader能找到,就不會(huì)去上層加載器加載

即雙親委派模式,但是其問(wèn)題也就是在于頂層classLoader 無(wú)法加載底層classLoader中的類

  • Java框架(rt.jar)如何加載應(yīng)用的類? 在app Loader中包含一個(gè)rt接口的實(shí)例
    javax.xml.parsers包中定義了xml解析的類接口,Service Provider Interface SPI 位于rt.jar 。即接口在啟動(dòng)ClassLoader中,而SPI的實(shí)現(xiàn)類,在AppLoader。
Thread. setContextClassLoader() 即上下文加載器

是一個(gè)角色/職責(zé),任何加載器都可以來(lái)承擔(dān)這個(gè)角色,就像班長(zhǎng)。
用以解決頂層ClassLoader無(wú)法訪問(wèn)底層ClassLoader的類的問(wèn)題
基本思想是,在頂層ClassLoader中,傳入底層ClassLoader的實(shí)例

rt.jar 中javax.xml.parsers.FactoryFinder展示如何在啟動(dòng)類加載器加載AppLoader的類 ,即上下文ClassLoader可以突破雙親模式的局限性

/**
     * Attempt to load a class using the class loader supplied. If that fails
     * and fall back is enabled, the current (i.e. bootstrap) class loader is
     * tried.
     *
     * If the class loader supplied is <code>null</code>, first try using the
     * context class loader followed by the current (i.e. bootstrap) class
     * loader.
     *
     * Use bootstrap classLoader if cl = null and useBSClsLoader is true
     */
    static private Class<?> getProviderClass(String className, ClassLoader cl,
            boolean doFallback, boolean useBSClsLoader) throws ClassNotFoundException
    {
        try {
            if (cl == null) {
                if (useBSClsLoader) {
                    return Class.forName(className, false, FactoryFinder.class.getClassLoader());
                } else {
                    cl = ss.getContextClassLoader();
                    if (cl == null) {
                        throw new ClassNotFoundException();
                    }
                    else {
                        return Class.forName(className, false, cl);
                    }
                }
            }
            else {
                return Class.forName(className, false, cl);
            }
        }
        catch (ClassNotFoundException e1) {
            if (doFallback) {
                // Use current class loader - should always be bootstrap CL
                return Class.forName(className, false, FactoryFinder.class.getClassLoader());
            }
            else {
                throw e1;
            }
        }
    }

打破常規(guī)模式

  • 雙親模式是默認(rèn)的模式,但不是必須這么做
  • Tomcat的WebappClassLoader 就會(huì)先加載自己的Class,找不到再委托parent
  • OSGi的ClassLoader形成網(wǎng)狀結(jié)構(gòu),根據(jù)需要自由加載Class
破壞雙親模式例子:

OrderClassLoaders

熱替換`當(dāng)一個(gè)class被替換后,系統(tǒng)無(wú)需重啟,替換的類立即生效

`

很不錯(cuò)的Java類加載器(ClassLoader) 推薦

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容