1. 程式人生 > >深入JVM類載入器

深入JVM類載入器

01、類載入器原理
02、類載入器樹狀結構、雙親委託(代理)機制
03、自定義類載入器(檔案、網路、加密)
04、執行緒上下文類載入器
05、伺服器類載入原理

1、類載入器的作用
將class檔案位元組碼內容載入到記憶體中,並將這些靜態資料轉換成方法區中的執行時資料結構,在堆中生成一個代表這個類的java.lang.Class物件,作為方法區類資料的訪問入口。
這裡寫圖片描述

類快取
標準的Java SE類載入器可以按要求查詢類,但一旦某個類載入到類載入器中,它將維持載入(快取)一段時間,不過JVM垃圾收集器可以回收這些Class物件。

2、類載入器的層次結構(樹狀結構)

引導類載入器(bootstarap class loader)(C)


- 它用來載入java的核心庫(JAVA_HOME/jre/lib/rt.jar或sun.boot.class.path路徑下的內容),是用原生程式碼來實現的,並不繼承自java.lang.ClassLoader。
- 載入擴充套件類和應用程式類載入器,並指定他們的父類載入器。

擴充套件類載入器(extensions class loader)(java)
- 用來載入java的擴充套件庫(JAVA_HOME/jre/ext/*.jar,或java.ext.dirs路徑下的內容)。Java虛擬機器的實現會提供一個擴充套件庫目錄,該類載入器在此目錄裡面查詢並載入java類。
- 由sun.misc.Launcher$ExtClassLoader實現

應用程式類載入器(application class loader)(java)
- 它根據java應用的類路徑(classpath ,java.class.path路徑類)
- 一般來說java應用的類都是由它來完成載入的
- 由sun.misc.Launcher$AppClassLoader實現

自定義類載入器(java寫的)
- 開發人員可以通過繼承java.lang.ClassLoader類的方法實現自己的類載入器,以滿足一些特殊的需求

這裡寫圖片描述

Java.class.ClassLoader類介紹
作用:
-Java.lang.ClassLoader類的基本職責就是根據一個指定的類的名稱,找到或者生成期對應的位元組程式碼,然後從這些位元組程式碼中定義一個java類,既java.lang.Class類的一個例項。
-除此之外,ClassLoader還負責載入java應用的所需資源,如影象檔案和配置檔案等。

相關方法
- getparent() 返回該類載入器的父類載入器
- loadClass(String name) 載入名稱為name的類,返回結果是java.lang.Class類的例項
- findClass(String name)查詢名稱為name的類,返回結果是java.lang.Class類的例項
- findLoadedClass(String name) 查詢名稱為name的已經被載入過的類,返回結果是java.lang.Class類的例項
- defineClass(String name,byte[] b,int off,int len)把位元組陣列b中的內容轉換成java類,返回的結果是java.lang.Class類的例項,這個方法被宣告final的。
- resolveClass(Class

package com.lyy.test;

public class Demo2 {

    public static void main(String[] args) {
        System.out.println(ClassLoader.getSystemClassLoader());
        System.out.println(ClassLoader.getSystemClassLoader().getParent());
        System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent()); //JAVA_HOME/jre/lib/rt.jar

        System.out.println(System.getProperty("java.class.path"));
        System.out.println("=====================================");
        String a = "gaogao";
        System.out.println(a.getClass().getClassLoader());
        System.out.println(a);

    }
}

類載入器的代理模式
代理模式
— 交給其他載入器來載入指定的類
雙親委託機制
— 就是某個特定的類載入器在接到載入類的請求時,首先將載入任務委託給父類載入器,一次追溯,知道最高的爺爺輩的,如果父類載入器可以完成類載入任務,就成功返回;只有父類載入器無法完成此載入任務時,才自己去載入。
— 雙親委託機制是為了保證java核心庫的型別安全。
這種機制就保證不會出現使用者自己能定義java.lang.Object類的情況。
類載入器除了使用者載入類,也是安全的最基本的屏障。
雙親委託機制是代理模式的一種
— 並不是所有的類載入器都採用雙親委託機制。
— Tomcat伺服器類載入器也使用代理模式,所不同的是它是首先嚐試去載入某個類,如果找不到再代理給父類載入器,這與一般類載入器的順序是相反的。

這裡寫圖片描述

自定義類載入器
自定義類載入器的流程
— 首先檢查請求的型別是否已經被這個類載入器裝載到名稱空間中了,
如果已經裝載,直接返回;
— 委派類載入請求給父類載入器能夠完成,則返回父類載入器載入的Class例項;
— 呼叫本類載入器的findClass(…)方法,檢視獲取對應的位元組碼,如果獲取到,則呼叫defineClass(…)匯入型別到方法區;如果獲取不到對應的位元組碼或者其他原因失敗,返回異常loadClass(…)loadClass(…)轉拋異常,終止載入過程。
— 注意:被兩個類載入器載入的同一個類,JVM不認為是相同的類

package com.lyy.temp;

public class HelloWrold {
public static void main(String[] args) {
    System.out.println("aaa");
}
}
package com.lyy.test;

/**
 * 測試自定義類載入器(FileSystemClassLoader)
 * @author 01
 *
 */
public class Demo3 {
    public static void main(String[] args) throws ClassNotFoundException {
        FileSystemClassLoader load = new FileSystemClassLoader("E:/VIP");
        FileSystemClassLoader load2 = new FileSystemClassLoader("E:/VIP");

        Class<?> c = load.loadClass("com.lyy.temp.HelloWrold");
        Class<?> c2 = load.loadClass("com.lyy.temp.HelloWrold");
        Class<?> c3 = load2.loadClass("com.lyy.temp.HelloWrold");

        Class<?> c4 = load2.loadClass("java.lang.String");
        Class<?> c5 = load2.loadClass("com.lyy.test.Demo1");

        System.out.println(c.hashCode());
        System.out.println(c2.hashCode());
        System.out.println(c3.hashCode());//同一個類,被不同的載入器載入,JVM認為也是不相同的類
        System.out.println(c4.hashCode());
        System.out.println(c3.getClassLoader());//自定義的類載入器
        System.out.println(c4.getClassLoader());//引導類載入器
        System.out.println(c5.getClassLoader());//系統預設的類載入器

    }
}

檔案類載入器

package com.lyy.test;

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

/**
 * 自定義檔案系統載入器
 * @author 01
 *
 */
public class FileSystemClassLoader extends ClassLoader{

    //com.lyy.test.User --> d:/myjava/com/lyy/test/User.class
    private String rootDir;

    public FileSystemClassLoader(String rootDir){
        this.rootDir=rootDir;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class<?> c = findLoadedClass(name);

        //首先查詢是否載入過該類,如果已經載入過,直接返回已經載入好的類,否則載入新的類
        if(null != c){
            return c;
        }else{
            ClassLoader parent = this.getParent();
            c = parent.loadClass(name); //委派給父類載入

            if(null != c){
                return c;
            }else{
                byte[] classData = getClassData(name);
                if(classData==null){
                    throw new ClassNotFoundException();
                }else{
                    c = defineClass(name, classData,0, classData.length);
                }
            }
        }
        return c;
    }

    private byte[] getClassData(String name) {  //com.lyy.test.User    d:/myjava/com/lyy/test/User.class
        String path = rootDir+"/"+name.replace('.', '/')+"class";

        //IOUtils,可以使用它將流中的資料轉成位元組資料
        InputStream is = null;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            is = new FileInputStream(path);

            byte[] buffer = new byte[1024];
            int temp = 0;
            while((temp=is.read(buffer)) != -1){
                baos.write(buffer,0,temp);
            }
            return baos.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }finally{
            try {
                if(is != null){
                    is.close();
                }
                if(baos != null){
                    baos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

網路類載入器

package com.lyy.test;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;

/**
 * 網路類載入器
 * @author 01
 *
 */
public class NetClassLoader extends ClassLoader{

    //com.lyy.test.User --> www.baidu.com
    private String rootUrl;

    public NetClassLoader(String rootUrl){
        this.rootUrl=rootUrl;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class<?> c = findLoadedClass(name);

        //首先查詢是否載入過該類,如果已經載入過,直接返回已經載入好的類,否則載入新的類
        if(null != c){
            return c;
        }else{
            ClassLoader parent = this.getParent();
            c = parent.loadClass(name); //委派給父類載入

            if(null != c){
                return c;
            }else{
                byte[] classData = getClassData(name);
                if(classData==null){
                    throw new ClassNotFoundException();
                }else{
                    c = defineClass(name, classData,0, classData.length);
                }
            }
        }
        return c;
    }

    private byte[] getClassData(String name) {  //com.lyy.test.User    d:/myjava/com/lyy/test/User.class
        String path = rootUrl+"/"+name.replace('.', '/')+"class";

        //IOUtils,可以使用它將流中的資料轉成位元組資料
        InputStream is = null;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            URL url = new URL(path);
            is = url.openStream();

            byte[] buffer = new byte[1024];
            int temp = 0;
            while((temp=is.read(buffer)) != -1){
                baos.write(buffer,0,temp);
            }
            return baos.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }finally{
            try {
                if(is != null){
                    is.close();
                }
                if(baos != null){
                    baos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

加密解密載入器(取反操作,DES對稱加密解密)

package com.lyy.test;

/**
 * 測試簡單加密解密(取反)操作
 * @author 01
 *
 */
public class Demo4 {

    public static void main(String[] args) throws Exception {
        //測試取反操作
//      int a = 3;//0000011
//      System.out.println(Integer.toBinaryString(a^0xff));

        //加密後的class檔案,正常的類載入器無法載入,報clasformatError
//      FileSystemClassLoader load = new FileSystemClassLoader("E:/VIP/temp");
//      Class<?> c = load.loadClass("HelloWrold");
//      System.out.println(c);

        DecrptClassLoader loader = new DecrptClassLoader("E:/VIP/temp");
        Class<?> c = loader.loadClass("com.lyy.temp.HelloWrold");
        System.out.println(c);
    }
}
package com.lyy.test;

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

/**
 * 自定義檔案系統載入器
 * @author 01
 *
 */
public class FileSystemClassLoader extends ClassLoader{

    //com.lyy.test.User --> d:/myjava/com/lyy/test/User.class
    private String rootDir;

    public FileSystemClassLoader(String rootDir){
        this.rootDir=rootDir;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class<?> c = findLoadedClass(name);

        //首先查詢是否載入過該類,如果已經載入過,直接返回已經載入好的類,否則載入新的類
        if(null != c){
            return c;
        }else{
            ClassLoader parent = this.getParent();
            c = parent.loadClass(name); //委派給父類載入

            if(null != c){
                return c;
            }else{
                byte[] classData = getClassData(name);
                if(classData==null){
                    throw new ClassNotFoundException();
                }else{
                    c = defineClass(name, classData,0, classData.length);
                }
            }
        }
        return c;
    }

    private byte[] getClassData(String name) {  //com.lyy.test.User    d:/myjava/com/lyy/test/User.class
        String path = rootDir+"/"+name.replace('.', '/')+"class";

        //IOUtils,可以使用它將流中的資料轉成位元組資料
        InputStream is = null;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            is = new FileInputStream(path);

            byte[] buffer = new byte[1024];
            int temp = 0;
            while((temp=is.read(buffer)) != -1){
                baos.write(buffer,0,temp);
            }
            return baos.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }finally{
            try {
                if(is != null){
                    is.close();
                }
                if(baos != null){
                    baos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
package com.lyy.test;

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

/**
 * 載入檔案系統加密後的class位元組碼的類載入器
 * @author 01
 *
 */
public class DecrptClassLoader extends ClassLoader{

    //com.lyy.test.User --> d:/myjava/com/lyy/test/User.class
    private String rootDir;

    public DecrptClassLoader(String rootDir){
        this.rootDir=rootDir;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class<?> c = findLoadedClass(name);

        //首先查詢是否載入過該類,如果已經載入過,直接返回已經載入好的類,否則載入新的類
        if(null != c){
            return c;
        }else{
            ClassLoader parent = this.getParent();
            c = parent.loadClass(name); //委派給父類載入

            if(null != c){
                return c;
            }else{
                byte[] classData = getClassData(name);
                if(classData==null){
                    throw new ClassNotFoundException();
                }else{
                    c = defineClass(name, classData,0, classData.length);
                }
            }
        }
        return c;
    }

    private byte[] getClassData(String name) {  //com.lyy.test.User    d:/myjava/com/lyy/test/User.class
        String path = rootDir+"/"+name.replace('.', '/')+"class";

        //IOUtils,可以使用它將流中的資料轉成位元組資料
        InputStream is = null;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            is = new FileInputStream(path);

            int temp = -1;
            while((temp=is.read()) != -1){
                baos.write(temp^0xff); //取反操作,相當於解密操作
            }

            return baos.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }finally{
            try {
                if(is != null){
                    is.close();
                }
                if(baos != null){
                    baos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

執行緒上下文類載入器
雙親委託機制以及類載入器的問題
一般情況下,保證同一個類中所關聯的其他類都是由當前類的類載入器所載入的。
比如,Class本身在Ext下找到,那麼他裡面new出來的一些類也就只能用ext去查找了(不會低一個級別),所以有些明明app可以找到的,卻找不到了。
JDBC API 他有實習那的driven部門(mysql/sql server),我們的JDBC API都是由Boot或者Ext來載入的,但是Service Prover卻是由EXT或者App來載入,那麼就有可能找不到driver了,在java領域中,其實只要分成這種Api-SPI(Service Provide Interface,特定廠商提供)的,都會遇到此問題。
常見的SPi 有JDBC、JCE、JNXP和JBI等。

package com.lyy.test;

/**
 * 執行緒上下文類載入器
 * @author 01
 *
 */
public class Demo5 {

    public static void main(String[] args) throws Exception {
        ClassLoader loader = Demo5.class.getClassLoader();
        System.out.println(loader);

        ClassLoader laoder2 = Thread.currentThread().getContextClassLoader();
        System.out.println(laoder2);

        Thread.currentThread().setContextClassLoader(new FileSystemClassLoader("E:/VIP/"));
        System.out.println(Thread.currentThread().getContextClassLoader());

        Class<Demo1> c = (Class<Demo1>)Thread.currentThread().getContextClassLoader().loadClass("com.lyy.test.Demo1");
        System.out.println(c);
        System.out.println(c.getClassLoader());

    }

}

TOMCAT伺服器的類載入機制
這裡寫圖片描述