JVM(3)-類加載機(jī)制

后來

1. 類加載的七個(gè)階段

1.1加載

  1. 通過一個(gè)類的全限定名來獲取定義此類的二進(jìn)制字節(jié)流;
  2. 將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu);
  3. 在內(nèi)存中生成一個(gè)代表這個(gè)類的java.lang.Class對(duì)象,作為方法區(qū)這個(gè)類的各種數(shù)據(jù)結(jié)構(gòu)的訪問入口;前面說大部分對(duì)象都是在堆中創(chuàng)建,但是對(duì)于HotSpot虛擬機(jī),這個(gè)對(duì)象存儲(chǔ)在方法區(qū)。

1.2 驗(yàn)證

  1. 文件格式驗(yàn)證
  2. 元數(shù)據(jù)驗(yàn)證
  3. 字節(jié)碼驗(yàn)證
  4. 符號(hào)引用驗(yàn)證

1.3 準(zhǔn)備

為類變量分配內(nèi)存(在方法區(qū))并設(shè)置類變量的初始值,通常是零值。

1.4 解析

虛擬機(jī)將常量池中的符號(hào)引用替換為直接引用的過程。

1.5 初始化

執(zhí)行類構(gòu)造器<clinit>()方法的過程。

1.6 使用

1.7 卸載

2. 類加載器

2.1 雙親委派模型-Parents delegation Model

Parents delegation Model
  1. 調(diào)用 findLoadedClass(String) 檢查這個(gè)類是否已經(jīng)加載過;
  2. 否則調(diào)用父加載器的 loadClass 去加載,如果父加載器為null,則使用啟動(dòng)類加載器作為父加載器Bootstrap Classloader;
  3. 否則調(diào)用自己的 findClass(String) 去加載這個(gè)類;

總的來說就是Class文件加載到類加載子系統(tǒng)后,先沿著圖中紅色虛線的方向自下而上進(jìn)行委托,再沿著黑色虛線的方向自上而下進(jìn)行查找,整個(gè)過程就是先上后下。

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException {
        // First, check if the class has already been loaded
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            try {
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }

            if (c == null) {
                // If still not found, then invoke findClass in order
                // to find the class.
                c = findClass(name);
            }
        }
        return c;
}

2.2 自定義ClassLoader

系統(tǒng)提供的類加載器只能夠加載指定目錄下的jar包和Class文件,如果想要加載網(wǎng)絡(luò)上的或者是D盤某一文件中的jar包和Class文件則需要自定義ClassLoader。
實(shí)現(xiàn)自定義ClassLoader需要兩個(gè)步驟:

  1. 定義一個(gè)自定義ClassLoade并繼承抽象類ClassLoader。
  2. 復(fù)寫findClass方法,并在findClass方法中調(diào)用defineClass方法。

下面我們就自定義一個(gè)ClassLoader用來加載位于D:\lib的Class文件。

2.2.1 編寫測(cè)試Class文件

首先編寫測(cè)試類并生成Class文件,如下所示。

package com.example;
public class Jobs {
    public void say() {
        System.out.println("One more thing");
    }
}

將這個(gè)Jobs.java放入到D:\lib中,使用cmd命令進(jìn)入D:\lib目錄中,執(zhí)行Javac Jobs.java對(duì)該java文件進(jìn)行編譯,這時(shí)會(huì)在D:\lib中生成Jobs.class。

2.2.2 編寫自定義ClassLoader

接下來編寫自定義ClassLoader,如下所示。

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class DiskClassLoader extends ClassLoader {
    private String path;
    public DiskClassLoader(String path) {
        this.path = path;
    }
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class clazz = null;
        byte[] classData = loadClassData(name);//1
        if (classData == null) {
            throw new ClassNotFoundException();
        } else {
            clazz= defineClass(name, classData, 0, classData.length);//2
        }
        return clazz;
    }
    private byte[] loadClassData(String name) {
        String fileName = getFileName(name);
        File file = new File(path,fileName);
        InputStream in=null;
        ByteArrayOutputStream out=null;
        try {
             in = new FileInputStream(file);
             out = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int length=0;
            while ((length = in.read(buffer)) != -1) {
                out.write(buffer, 0, length);
            }
            return out.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                if(in!=null) {
                    in.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try{
                if(out!=null) {
                    out.close();
                }
            }catch (IOException e){
                e.printStackTrace();
            }
        }
        return null;
    }
    private String getFileName(String name) {
        int index = name.lastIndexOf('.');
        if(index == -1){//如果沒有找到'.'則直接在末尾添加.class
            return name+".class";
        }else{
            return name.substring(index+1)+".class";
        }
    }
}

這段代碼有幾點(diǎn)需要注意的,注釋1處的loadClassData方法會(huì)獲得class文件的字節(jié)碼數(shù)組,并在注釋2處調(diào)用defineClass方法將class文件的字節(jié)碼數(shù)組轉(zhuǎn)為Class類的實(shí)例。loadClassData方法中需要對(duì)流進(jìn)行操作,關(guān)閉流的操作要放在finally語句塊中,并且要對(duì)in和out分別采用try語句,如果in和out共同在一個(gè)try語句中,那么如果in.close()發(fā)生異常,則無法執(zhí)行 out.close()。

最后我們來驗(yàn)證DiskClassLoader是否可用,代碼如下所示。

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ClassLoaderTest {
    public static void main(String[] args) {
        DiskClassLoader diskClassLoader = new DiskClassLoader("D:\\lib");//1
        try {
            Class c = diskClassLoader.loadClass("com.example.Jobs");//2
            if (c != null) {
                try {
                    Object obj = c.newInstance();
                    System.out.println(obj.getClass().getClassLoader());
                    Method method = c.getDeclaredMethod("say", null);
                    method.invoke(obj, null);//3
                } catch (InstantiationException | IllegalAccessException
                        | NoSuchMethodException
                        | SecurityException |
                        IllegalArgumentException |
                        InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

注釋1出創(chuàng)建DiskClassLoader并傳入要加載類的路徑,注釋2處加載Class文件,需要注意的是,不要在項(xiàng)目工程中存在名為com.example.Jobs的Java文件,否則就不會(huì)使用DiskClassLoader來加載,而是AppClassLoader來負(fù)責(zé)加載,這樣我們定義DiskClassLoader就變得毫無意義。接下來在注釋3通過反射來調(diào)用Jobs的say方法,打印結(jié)果如下:

com.example.DiskClassLoader@4554617c
One more thing

使用了DiskClassLoader來加載Class文件,say方法也正確執(zhí)行,顯然我們的目的達(dá)到了。

關(guān)注微信公眾號(hào),第一時(shí)間接收推送!
最后編輯于
?著作權(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)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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