【JVM】類加載器及雙親委派機制實例解析

1、類加載器及雙親委派機制介紹

在JVM中,一個類被加載到虛擬機這個過程包括有3個步驟,即加載、鏈接和初始化。而加載這個過程,就是由類加載器ClassLoader進行加載的,類加載器天生就負責這個職責。java

Java自己給咱們提供了幾種類型的類加載器,啓動類加載器Bootstrap ClassLoader、擴展類加載器Extension ClassLoader、應用類加載器App ClassLoader,除了上面3種,咱們也能夠定義咱們本身的類加載器,此時咱們只須要繼承ClassLoader類,重寫其findClass()方法便可。數組

固然,有時候,你覺得的並非你覺得的,當咱們用自定義類加載器去加載類路徑ClassPath下某個class文件時,而後調用該Class對象的getClassLoader()方法時,咱們會發現,加載該類的類加載器,並非咱們自定義的加載器,至於爲何?下文將會講到。 下圖是JVM類加載器機制bash

  • 一、啓動類加載器Bootstrap ClassLoader網絡

    啓動類加載器,負責%JRE_HOME/lib/目錄下的相關類文件,好比rt.jartools.jar等等,好比說咱們的String類存放在rt.jar中,器加載器就是Bootstrap ClassLoadereclipse

  • 二、擴展類加載器Extension ClassLoaderide

    擴展類加載器,負責%JRE_HOME/lib/ext目錄下的相關類文件,好比rt.jartools.jar等等工具

  • 三、應用類加載器App ClassLoader開發工具

    應用類加載器,負責加載應用程序類路徑下的class文件。ui

說到類路徑的問題,咱們解釋一下,java的類路徑指的是咱們配置的系統環境變量的值:CLASSPATH,但又不侷限於這個值。小編先打印下咱們本地的classpath的值: this

.;D:\development\jdk\lib\dt.jar;D:\development\jdk\lib\tools.jar;

你會發現,這根本就不是咱們用的開發工具中的目錄呀,那麼JVM是怎麼加載到咱們在eclipse上編寫的類呢?答案是eclipse已經幫咱們弄好了一切。

下邊咱們舉個例子,看下String類的類加載器是什麼:

System.out.println(String.class.getClassLoader());

上面這句代碼輸出null。爲何呢?由於若是一個類是被Bootstrap ClassLoader或者Extension ClassLoader加載時,getClassLoader()規定輸出null。又由於String類存在rt.jar中,將會被Bootstrap ClassLoader加載,因此輸出null

談及類加載器,咱們不得不說類的雙親委派機制,一句話總結雙親委派機制,小編總結成一句話:

若是自定義加載器P有父加載器P1,那麼在加載前就將加載任務委派給其父親P1,若是P1也存在父加載器P2,那麼將加載任務委派給P2,若是最頂層的Bootstrap ClassLoader還加載不到,那麼就再逆着順序加載,直到類被加載到~~

2、ClassLoader類特性介紹

  • 一、每個Class實例都包含一個ClassLoader引用

    所以,咱們老是能經過Class對象的getClassLoader()方法獲取其類加載器。固然了,就如上面說的,若是一個類是被Bootstrap ClassLoader或者Extension ClassLoader加載時,getClassLoader()規定輸出null。這是一個須要咱們注意的點。

  • 二、對於數組類型[]對象,它們不是由類加載器ClassLoader去進行加載的,而是Java虛擬機根據須要自動建立的。咱們經過數組類型的Class對象的getClassLoader()方法返回的值跟數組裏邊元素所使用的的類加載器同樣,但若是數組元素爲原始類型int啥的,則getClassLoader()方法將返回null

    // sun.misc.Launcher$AppClassLoader@73d16e93
    ClassLoaderTest[] array = new ClassLoaderTest[5];
    System.out.println(array.getClass().getClassLoader());
    // null
    int[] intArray = new int[3];
    System.out.println(intArray.getClass().getClassLoader());
    複製代碼
  • 三、咱們能夠經過繼承ClassLoader類,來實現本身的類加載器

/**
 * 自定義類加載器
 * @Author jiawei huang
 * @Since 2019年8月7日
 * @Version 1.0
 */
public class MyClassLoader extends ClassLoader {
    // 若是咱們從其餘地方進行加載,咱們能夠指定路徑
    private String classpath;
    public MyClassLoader(String classPath) {
    	classpath = classPath;
    }
    public MyClassLoader() {
    }
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
    	Class clazz = null;
    	// 獲取該class文件字節碼數組
    	byte[] classData = getClassData();
    	if (classData != null) {
    		// 將class的字節碼數組轉換成Class類的實例
    		clazz = defineClass(name, classData, 0, classData.length);
    	}
    	return clazz;
    }
    
    private byte[] getClassData() {
    	byte[] bytes = null;
    	File file = new File(classpath);
    	if (file.exists()) {
    		// 從文件中讀取class字節數據
    		FileInputStream in = null;
    		ByteArrayOutputStream out = null;
    		try {
    			in = new FileInputStream(file);
    			out = new ByteArrayOutputStream();
    
    			byte[] buffer = new byte[1024];
    			int size = 0;
    			while ((size = in.read(buffer)) != -1) {
    				out.write(buffer, 0, size);
    			}
    		} catch (IOException e) {
    			e.printStackTrace();
    		} finally {
    			try {
    				in.close();
    			} catch (IOException e) {
    				e.printStackTrace();
    			}
    		}
    		bytes = out.toByteArray();
    	}
    	return bytes;
    }
}
複製代碼

假設咱們在ex包路徑下有個Main.java,看看咱們下面的輸出:

A.java
MyClassLoader loader = new MyClassLoader();
Class<?> clazzClass = loader.loadClass("ex.Main");
// 1
System.out.println(clazzClass.getClassLoader());
複製代碼

上面1處將輸出sun.misc.Launcher$AppClassLoader@73d16e93,咱們可能會問,明明我是用本身的類加載器去加載ex.Main的呀,爲何卻輸出AppClassLoader這個類加載器呢?其實這就是雙親委派機制,MyClassLoader把加載任務給到其父加載器App ClassLoader,恰好ex.Main又處於類路徑下,因此App ClassLoader加載以後就直接返回了。

  • 四、一個類整個生命週期只會被加載一次,有且僅有一次

  • 五、支持並行加載的加載器稱爲並行加載器,但前提是,咱們必須在自定義加載器的初始化時,調用ClassLoader.registerAsParallelCapable();方法註冊本身,怎麼作呢?咱們看看URLClassLoader的源碼。

static {
    // 註冊本身
    ClassLoader.registerAsParallelCapable();
}
複製代碼
  • 六、在委託模型不是嚴格分層的狀況下,自定義類加載器須要具備並行能力,不然類加載可能致使死鎖,由於加載器鎖在類加載過程當中一直持有。

詳情咱們看下ClassLoaderloadClass()方法源碼:

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    // 這裏有個鎖
    synchronized (getClassLoadingLock(name)) {
        一、先檢查這個名稱的類是否已經被加載過,若是是,就再也不加載了,這也印證了咱們第4點說的一個類只會被加載一次
        Class<?> c = findLoadedClass(name);
        // 二、若是該類沒有被加載,那麼就進行加載
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                // 三、若是存在父加載器,就進行委派,這就是雙親委派機制的原理
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    // 四、去`Bootstrap classloader`加載
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }
            // 五、若是3,4都沒有加載到,那就執行咱們自定義的classloader,這也是爲何咱們要重寫findClass方法的緣由所在
            if (c == null) {
                // If still not found, then invoke findClass in order
                // to find the class.
                long t1 = System.nanoTime();
                c = findClass(name);
    
                // this is the defining class loader; record the stats
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}
複製代碼
  • 七、咱們不只僅能夠從文件系統中對類進行加載,咱們還能夠從網絡上進行class文件的加載,獲取byte[],而後經過ClassLoaderdefineClass()byte[]轉換成Class對象,最後經過Class.newInstance()便可轉成Java對象啦~~~
ClassLoader loader = new NetworkClassLoader(host, port);
Object main = loader.loadClass("Main", true).newInstance();
複製代碼

3、SpringBoot中,構造器爲何要加上ClassLoader參數?

由於初始化過程當中,load()方法須要從不一樣的地方去加載類文件。

protected void load(ApplicationContext context, Object[] sources) {
    if (logger.isDebugEnabled()) {
    	logger.debug(
    			"Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
    }
    BeanDefinitionLoader loader = createBeanDefinitionLoader(
    		getBeanDefinitionRegistry(context), sources);
    if (this.beanNameGenerator != null) {
    	loader.setBeanNameGenerator(this.beanNameGenerator);
    }
    if (this.resourceLoader != null) {
    	loader.setResourceLoader(this.resourceLoader);
    }
    if (this.environment != null) {
    	loader.setEnvironment(this.environment);
    }
    loader.load();
}
複製代碼
相關文章
相關標籤/搜索