1. 程式人生 > >jvm原理(16)類載入器實戰剖析與疑難點解析

jvm原理(16)類載入器實戰剖析與疑難點解析

三大類載入器所載入的路徑範圍:

public class MyTest18 {
    public static void main(String[] args) {
        System.out.println(System.getProperty("sun.boot.class.path"));//系統類載入器載入路徑
        System.out.println(System.getProperty("java.ext.dirs"));//擴充套件類載入器載入路徑
        System.out.println(System.getProperty("java.class.path"
));//應用類載入器載入過程 } }

結果(根據自身環境不同會有所差異):

C:\Program Files\Java\jdk1.8.0_111\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\rt.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\sunrsasign.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\jce
.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_111\jre\classes ------------------------------------------- C:\Program Files\Java\jdk1.8.0_111\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext ------------------------------------------- C:\Program
Files\Java\jdk1.8.0_111\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_111\jre\lib\rt.jar;E:\Study\intelIde\jvm_lecture\out\production\classes;D:\IntelliJ IDEA 2017.2.4\lib\idea_rt.jar

下面我寫一個程式:

public class MyTest18_1 {
    public static void main(String[] args) throws Exception {
        MyTest16 loader1 = new MyTest16("loader1");
        loader1.setPath("E:\\data\\classes\\");
        Class<?> clazz = loader1.loadClass("com.twodragonlake.jvm.classloader.MyTest");
        System.out.println("class : "+clazz.hashCode());
        System.out.println("class loader :" + clazz.getClassLoader());
    }
}

列印結果:

class : 1735600054
class loader :sun.misc.Launcher$AppClassLoader@18b4aac2

MyTest是由應用類載入器載入,這個沒啥毛病,那麼我們能不能想辦法讓啟動類載入器去載入MyTest呢?要想讓啟動類載入器載入MyTest必須讓啟動類載入器能夠找到MyTest的class檔案,那就需要把MyTest放到啟動類載入器的載入目錄裡邊。ok,那麼我們找到啟動類載入器的一個路徑比如【C:\Program Files\Java\jdk1.8.0_111\jre\classes】我們定位到此目錄下邊,此時你會發現C:\Program Files\Java\jdk1.8.0_111\jre\下邊並沒有classes資料夾,我們新建一個classes即可,既然sun.boot.class.path變數指定了這個目錄我們就能從裡邊嘗試載入類,然後我們把工程下邊的【com.twodragonlake.jvm.classloader.MyTest.class】放到C:\Program Files\Java\jdk1.8.0_111\jre\下邊,
這裡寫圖片描述
OK,此時我們執行MyTest18_1 得到列印結果:

class : 2133927002
class loader :null

null表明是由啟動類載入器載入。即,MyTest.class是由啟動類載入器載入。做完實驗時候可以把classes目錄刪除,以免造成混繞。

接下來看一下擴充套件類載入器:


public class MyTest19 {
    public static void main(String[] args) {
        AESKeyGenerator aesKeyGenerator = new AESKeyGenerator();
        System.out.println(aesKeyGenerator.getClass().getClassLoader());
        System.out.println(MyTest19.class.getClassLoader());
    }
}

列印結果:

sun.misc.Launcher$ExtClassLoader@12a3a380
sun.misc.Launcher$AppClassLoader@18b4aac2

那好我們能不能修改擴充套件類載入器的載入路徑java.ext.dirs的值,讓其從當前目錄載入如何?
這裡寫圖片描述
顯然AESKeyGenerator無法被載入,因為當前目錄不存在這個類。

下一個例子:

public class MyTest20 {
    public static void main(String[] args)  throws Exception{
        MyTest16 loader1 = new MyTest16("loader1") ;
        MyTest16 loader2 = new MyTest16("loader2") ;
        Class<?> clazz1 = loader1.loadClass("com.twodragonlake.jvm.classloader.MyPerson");
        Class<?> clazz2 = loader2.loadClass("com.twodragonlake.jvm.classloader.MyPerson");
        System.out.println(clazz1 ==  clazz2);
        Object object1 = clazz1.newInstance();
        Object object2 = clazz2.newInstance();

        Method method   = clazz1.getMethod("setMyPerson",Object.class);
        method.invoke(object1,object2);
    }
}

列印結果為true,原因是雖然loader1和loader2是2個不同的類載入器例項,但是他們都會委託應用類載入器去載入,如果應用類載入器載入過MyPerson,第二次不會再次載入。所以這2個載入器載入的類返回的結果是同一個類。
下邊的我們用clazz1建立的Method,然後呼叫的時候我們傳入的是clazz2的物件object2,由於object1和object2的class是同一個class,所以向下型別轉換不會出現問題。