欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

JVM要雙親委派的原因及如何打破它

 更新時間:2021年06月08日 09:28:33   作者:徐同學(xué)呀  
平時做業(yè)務(wù)開發(fā)比較少接觸類加載器,但是如果想深入學(xué)習(xí),了解類加載的原理是必不可少的.java的類加載器有哪些?什么是雙親委派?為什么要雙親委派?如何打破它?接下來本文就帶大家詳細(xì)介紹這些知識 ,需要的朋友可以參考下

一、類加載器

類加載器,顧名思義就是一個可以將Java字節(jié)碼加載為java.lang.Class實例的工具。這個過程包括,讀取字節(jié)數(shù)組、驗證、解析、初始化等。另外,它也可以加載資源,包括圖像文件和配置文件。

類加載器的特點:

  • 動態(tài)加載,無需在程序一開始運行的時候加載,而是在程序運行的過程中,動態(tài)按需加載,字節(jié)碼的來源也很多,壓縮包jar、war中,網(wǎng)絡(luò)中,本地文件等。類加載器動態(tài)加載的特點為熱部署,熱加載做了有力支持。
  • 全盤負(fù)責(zé),當(dāng)一個類加載器加載一個類時,這個類所依賴的、引用的其他所有類都由這個類加載器加載,除非在程序中顯式地指定另外一個類加載器加載。所以破壞雙親委派不能破壞擴(kuò)展類加載器以上的順序。

一個類的唯一性由加載它的類加載器和這個類的本身決定(類的全限定名+類加載器的實例ID作為唯一標(biāo)識)。比較兩個類是否相等(包括Class對象的equals()、isAssignableFrom()、isInstance()以及instanceof關(guān)鍵字等),只有在這兩個類是由同一個類加載器加載的前提下才有意義,否則,即使這兩個類來源于同一個Class文件,被同一個虛擬機(jī)加載,只要加載它們的類加載器不同,這兩個類就必定不相等。

從實現(xiàn)方式上,類加載器可以分為兩種:一種是啟動類加載器,由C++語言實現(xiàn),是虛擬機(jī)自身的一部分;另一種是繼承于java.lang.ClassLoader的類加載器,包括擴(kuò)展類加載器、應(yīng)用程序類加載器以及自定義類加載器。

啟動類加載器Bootstrap ClassLoader):負(fù)責(zé)加載<JAVA_HOME>\lib目錄中的,或者被-Xbootclasspath參數(shù)所指定的路徑,并且是虛擬機(jī)識別的(僅按照文件名識別,如rt.jar,名字不符合的類庫即使放在lib目錄中也不會被加載)類庫加載到虛擬機(jī)內(nèi)存中。啟動類加載器無法被Java程序直接引用,用戶在編寫自定義類加載器時,如果想設(shè)置Bootstrap ClassLoader為其parent,可直接設(shè)置null

擴(kuò)展類加載器Extension ClassLoader):負(fù)責(zé)加載<JAVA_HOME>\lib\ext目錄中的,或者被java.ext.dirs系統(tǒng)變量所指定路徑中的所有類庫。該類加載器由sun.misc.Launcher$ExtClassLoader實現(xiàn)。擴(kuò)展類加載器由啟動類加載器加載,其父類加載器為啟動類加載器,即parent=null。

應(yīng)用程序類加載器Application ClassLoader):負(fù)責(zé)加載用戶類路徑(ClassPath)上所指定的類庫,由sun.misc.Launcher$App-ClassLoader實現(xiàn)。開發(fā)者可直接通過java.lang.ClassLoader中的getSystemClassLoader()方法獲取應(yīng)用程序類加載器,所以也可稱它為系統(tǒng)類加載器。應(yīng)用程序類加載器也是啟動類加載器加載的,但是它的父類加載器是擴(kuò)展類加載器。在一個應(yīng)用程序中,系統(tǒng)類加載器一般是默認(rèn)類加載器。

二、雙親委派機(jī)制

2.1 什么是雙親委派

JVM 并不是在啟動時就把所有的.class文件都加載一遍,而是程序在運行過程中用到了這個類才去加載。除了啟動類加載器外,其他所有類加載器都需要繼承抽象類ClassLoader,這個抽象類中定義了三個關(guān)鍵方法,理解清楚它們的作用和關(guān)系非常重要。

public abstract class ClassLoader {

    //每個類加載器都有個父加載器
    private final ClassLoader parent;
    
    public Class<?> loadClass(String name) {
  
        //查找一下這個類是不是已經(jīng)加載過了
        Class<?> c = findLoadedClass(name);
        
        //如果沒有加載過
        if( c == null ){
          //先委派給父加載器去加載,注意這是個遞歸調(diào)用
          if (parent != null) {
              c = parent.loadClass(name);
          }else {
              // 如果父加載器為空,查找Bootstrap加載器是不是加載過了
              c = findBootstrapClassOrNull(name);
          }
        }
        // 如果父加載器沒加載成功,調(diào)用自己的findClass去加載
        if (c == null) {
            c = findClass(name);
        }
        
        return c;
    }
    
    protected Class<?> findClass(String name){
       //1. 根據(jù)傳入的類名name,到在特定目錄下去尋找類文件,把.class文件讀入內(nèi)存
          ...
          
       //2. 調(diào)用defineClass將字節(jié)數(shù)組轉(zhuǎn)成Class對象
       return defineClass(buf, off, len);
    }
    
    // 將字節(jié)碼數(shù)組解析成一個Class對象,用native方法實現(xiàn)
    protected final Class<?> defineClass(byte[] b, int off, int len){
       ...
    }
}

從上面的代碼可以得到幾個關(guān)鍵信息:

  • JVM 的類加載器是分層次的,它們有父子關(guān)系,而這個關(guān)系不是繼承維護(hù),而是組合,每個類加載器都持有一個 parent字段,指向父加載器。(AppClassLoaderparentExtClassLoaderExtClassLoaderparentBootstrapClassLoader,但是ExtClassLoaderparent=null。)
  • defineClass方法的職責(zé)是調(diào)用 native 方法把 Java 類的字節(jié)碼解析成一個 Class 對象。
  • findClass方法的主要職責(zé)就是找到.class文件并把.class文件讀到內(nèi)存得到字節(jié)碼數(shù)組,然后調(diào)用 defineClass方法得到 Class 對象。子類必須實現(xiàn)findClass。
  • loadClass方法的主要職責(zé)就是實現(xiàn)雙親委派機(jī)制:首先檢查這個類是不是已經(jīng)被加載過了,如果加載過了直接返回,否則委派給父加載器加載,這是一個遞歸調(diào)用,一層一層向上委派,最頂層的類加載器(啟動類加載器)無法加載該類時,再一層一層向下委派給子類加載器加載。

雙親委派模型

2.2 為什么要雙親委派?

雙親委派保證類加載器,自下而上的委派,又自上而下的加載,保證每一個類在各個類加載器中都是同一個類。

一個非常明顯的目的就是保證java官方的類庫<JAVA_HOME>\lib和擴(kuò)展類庫<JAVA_HOME>\lib\ext的加載安全性,不會被開發(fā)者覆蓋。

例如類java.lang.Object,它存放在rt.jar之中,無論哪個類加載器要加載這個類,最終都是委派給啟動類加載器加載,因此Object類在程序的各種類加載器環(huán)境中都是同一個類。

如果開發(fā)者自己開發(fā)開源框架,也可以自定義類加載器,利用雙親委派模型,保護(hù)自己框架需要加載的類不被應(yīng)用程序覆蓋。

三、破壞雙親委派

如果想自定義類加載器,就需要繼承ClassLoader,并重寫findClass,如果想不遵循雙親委派的類加載順序,還需要重寫loadClass。如下是一個自定義的類加載器,并重寫了loadClass破壞雙親委派:

package com.stefan.DailyTest.classLoader;

import java.io.*;

public class TestClassLoader extends ClassLoader {
    public TestClassLoader(ClassLoader parent) {
        super(parent);
    }
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        // 1、獲取class文件二進(jìn)制字節(jié)數(shù)組
        byte[] data = null;
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            FileInputStream fis = new FileInputStream(new File("C:\\study\\myStudy\\JavaLearning\\target\\classes\\com\\stefan\\DailyTest\\classLoader\\Demo.class"));
            byte[] bytes = new byte[1024];
            int len = 0;
            while ((len = fis.read(bytes)) != -1) {
                baos.write(bytes, 0, len);
            }
            data = baos.toByteArray();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 2、字節(jié)碼數(shù)組加載到 JVM 的方法區(qū),
        // 并在 JVM 的堆區(qū)建立一個java.lang.Class對象的實例
        // 用來封裝 Java 類相關(guān)的數(shù)據(jù)和方法
        return this.defineClass(name, data, 0, data.length);
    }

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException{
        // 1、找到ext classLoader,并首先委派給它加載,為什么?
        ClassLoader classLoader = getSystemClassLoader();
        while (classLoader.getParent() != null) {
            classLoader = classLoader.getParent();
        }
        Class<?> clazz = null;
        try {
            clazz = classLoader.loadClass(name);
        } catch (ClassNotFoundException e) {
            // Ignore
        }
        if (clazz != null) {
            return clazz;
        }
        // 2、自己加載
        clazz = this.findClass(name);
        if (clazz != null) {
            return clazz;
        }
        // 3、自己加載不了,再調(diào)用父類loadClass,保持雙親委派模式
        return super.loadClass(name);
    }
}

破壞雙親委派

測試加載Demo類:

package com.stefan.DailyTest.classLoader;

public class Test {
    public static void main(String[] args) throws Exception {
        // 初始化TestClassLoader,并將加載TestClassLoader類的類加載器
        // 設(shè)置為TestClassLoader的parent
        TestClassLoader testClassLoader = new TestClassLoader(TestClassLoader.class.getClassLoader());
        System.out.println("TestClassLoader的父類加載器:" + testClassLoader.getParent());
        // 加載 Demo
        Class clazz = testClassLoader.loadClass("com.stefan.DailyTest.classLoader.Demo");
        System.out.println("Demo的類加載器:" + clazz.getClassLoader());
    }
}

//控制臺打印
TestClassLoader的父類加載器:sun.misc.Launcher$AppClassLoader@18b4aac2
Demo的類加載器:com.stefan.DailyTest.classLoader.TestClassLoader@78308db1

注意破壞雙親委派的位置,自定義類加載機(jī)制先委派給ExtClassLoader加載,ExtClassLoader再委派給BootstrapClassLoader,如果都加載不了,然后自定義類加載器加載,自定義類加載器加載不了才交給AppClassLoader。為什么不能直接讓自定義類加載器加載呢?

不能!雙親委派的破壞只能發(fā)生在AppClassLoader及其以下的加載委派順序,ExtClassLoader上面的雙親委派是不能破壞的!

因為任何類都是繼承自超類java.lang.Object,而加載一個類時,也會加載繼承的類,如果該類中還引用了其他類,則按需加載,且類加載器都是加載當(dāng)前類的類加載器。

Demo類只隱式繼承了Object,自定義類加載器TestClassLoader加載了Demo,也會加載Object。如果loadClass直接調(diào)用TestClassLoaderfindClass會報錯java.lang.SecurityException: Prohibited package name: java.lang

為了安全,java是不允許除BootStrapClassLOader以外的類加載器加載官方java.目錄下的類庫的。在defineClass源碼中,最終會調(diào)用native方法defineClass1獲取Class對象,在這之前會檢查類的全限定名name是否是java.開頭。(如果想完全繞開java的類加載,需要自己實現(xiàn)defineClass,但是因為個人能力有限,沒有深入研究defineClass的重寫,并且一般情況也不會破壞ExtClassLoader以上的雙親委派,除非不用java了。)

defineClass

preDefineClass

通過自定義類加載器破壞雙親委派的案例在日常開發(fā)中非常常見,比如Tomcat為了實現(xiàn)web應(yīng)用間加載隔離,自定義了類加載器,每個Context代表一個web應(yīng)用,都有一個webappClassLoader。再如熱部署、熱加載的實現(xiàn)都是需要自定義類加載器的。破壞的位置都是跳過AppClassLoader。

四、Class.forName默認(rèn)使用的類加載器

1. forName(String name, boolean initialize,ClassLoader loader)可以指定classLoader。

2.不顯式傳classLoader就是默認(rèn)當(dāng)前類的類加載器:

public static Class<?> forName(String className)
                throws ClassNotFoundException {
      Class<?> caller = Reflection.getCallerClass();
      return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}

五、線程上下文類加載器

線程上下文類加載器其實是一種類加載器傳遞機(jī)制??梢酝ㄟ^java.lang.Thread#setContextClassLoader方法給一個線程設(shè)置上下文類加載器,在該線程后續(xù)執(zhí)行過程中就能把這個類加載器?。?code>java.lang.Thread#getContextClassLoader)出來使用。

如果創(chuàng)建線程時未設(shè)置上下文類加載器,將會從父線程(parent = currentThread())中獲取,如果在應(yīng)用程序的全局范圍內(nèi)都沒有設(shè)置過,就默認(rèn)是應(yīng)用程序類加載器。

線程上下文類加載器的出現(xiàn)就是為了方便破壞雙親委派:

一個典型的例子便是JNDI服務(wù),JNDI現(xiàn)在已經(jīng)是Java的標(biāo)準(zhǔn)服務(wù),它的代碼由啟動類加載器去加載(在JDK 1.3時放進(jìn)去的rt.jar),但JNDI的目的就是對資源進(jìn)行集中管理和查找,它需要調(diào)用由獨立廠商實現(xiàn)并部署在應(yīng)用程序的ClassPath下的JNDI接口提供者(SPI,Service Provider Interface)的代碼,但啟動類加載器不可能去加載ClassPath下的類。

但是有了線程上下文類加載器就好辦了,JNDI服務(wù)使用線程上下文類加載器去加載所需要的SPI代碼,也就是父類加載器請求子類加載器去完成類加載的動作,這種行為實際上就是打通了雙親委派模型的層次結(jié)構(gòu)來逆向使用類加載器,實際上已經(jīng)違背了雙親委派模型的一般性原則,但這也是無可奈何的事情。

Java中所有涉及SPI的加載動作基本上都采用這種方式,例如JNDI、JDBC、JCE、JAXB和JBI等。

摘自《深入理解java虛擬機(jī)》周志明

六、要點回顧

1.java 的類加載,就是獲取.class文件的二進(jìn)制字節(jié)碼數(shù)組并加載到 JVM 的方法區(qū),并在 JVM 的堆區(qū)建立一個用來封裝 java 類相關(guān)的數(shù)據(jù)和方法的java.lang.Class對象實例。

2.java默認(rèn)有的類加載器有三個,啟動類加載器(BootstrapClassLoader),擴(kuò)展類加載器(ExtClassLoader),應(yīng)用程序類加載器(也叫系統(tǒng)類加載器)(AppClassLoader)。類加載器之間存在父子關(guān)系,這種關(guān)系不是繼承關(guān)系,是組合關(guān)系。如果parent=null,則它的父級就是啟動類加載器。啟動類加載器無法被java程序直接引用。

3.雙親委派就是類加載器之間的層級關(guān)系,加載類的過程是一個遞歸調(diào)用的過程,首先一層一層向上委托父類加載器加載,直到到達(dá)最頂層啟動類加載器,啟動類加載器無法加載時,再一層一層向下委托給子類加載器加載。

4.雙親委派的目的主要是為了保證java官方的類庫<JAVA_HOME>\lib和擴(kuò)展類庫<JAVA_HOME>\lib\ext的加載安全性,不會被開發(fā)者覆蓋。

5.破壞雙親委派有兩種方式:第一種,自定義類加載器,必須重寫findClassloadClass;第二種是通過線程上下文類加載器的傳遞性,讓父類加載器中調(diào)用子類加載器的加載動作。

參考:

  • 《深入理解java虛擬機(jī)》周志明(書中對類加載的介紹非常詳盡,部分精簡整理后引用。)
  • 《深入拆解Tomcat & Jetty》Tomcat如何打破雙親委托機(jī)制?
  • 李號雙《Tomcat內(nèi)核設(shè)計剖析》汪建,第十三章 公共與隔離的類加載

到此這篇關(guān)于JVM要雙親委派的原因及如何打破它的文章就介紹到這了,更多相關(guān)JVM雙親委派內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • groovy腳本定義結(jié)構(gòu)表一鍵生成POJO類

    groovy腳本定義結(jié)構(gòu)表一鍵生成POJO類

    這篇文章主要為大家介紹了groovy腳本定義結(jié)構(gòu)表一鍵生成POJO類示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-03-03
  • 基于Spring中的線程池和定時任務(wù)功能解析

    基于Spring中的線程池和定時任務(wù)功能解析

    下面小編就為大家?guī)硪黄赟pring中的線程池和定時任務(wù)功能解析。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-09-09
  • Java實現(xiàn)開箱即用的redis分布式鎖

    Java實現(xiàn)開箱即用的redis分布式鎖

    這篇文章主要為大家詳細(xì)介紹了如何使用Java實現(xiàn)開箱即用的基于redis的分布式鎖,文中的示例代碼講解詳細(xì),具有一定的借鑒價值,需要的可以收藏一下
    2022-12-12
  • java分割字符串多種方法(附例子)

    java分割字符串多種方法(附例子)

    這篇文章主要給大家介紹了關(guān)于java分割字符串多種方法的相關(guān)資料,Java中有多種方法可以實現(xiàn)字符串分割,文中將每張方法都給出了代碼示例,需要的朋友可以參考下
    2023-10-10
  • Springboot如何優(yōu)雅地進(jìn)行字段校驗

    Springboot如何優(yōu)雅地進(jìn)行字段校驗

    這篇文章主要給大家介紹了關(guān)于Springboot如何優(yōu)雅地進(jìn)行字段校驗的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-01-01
  • Java利用反射自動封裝成實體對象的方法

    Java利用反射自動封裝成實體對象的方法

    這篇文章主要介紹了Java利用反射自動封裝成實體對象的方法,可實現(xiàn)自動封裝成bean對象功能,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-01-01
  • Java8函數(shù)式編程應(yīng)用小結(jié)

    Java8函數(shù)式編程應(yīng)用小結(jié)

    Java8非常重要的就是引入了函數(shù)式編程的思想,使得這門經(jīng)典的面向?qū)ο笳Z言有了函數(shù)式的編程方式,彌補(bǔ)了很大程度上的不足,函數(shù)式思想在處理復(fù)雜問題上有著更為令人稱贊的特性,本文給大家介紹Java8函數(shù)式編程應(yīng)用小結(jié),感興趣的朋友一起看看吧
    2023-12-12
  • 教你使用springboot配置多數(shù)據(jù)源

    教你使用springboot配置多數(shù)據(jù)源

    發(fā)現(xiàn)有很多小伙伴還不會用springboot配置多數(shù)據(jù)源,今天特地給大家整理了本篇文章,文中有非常詳細(xì)的圖文介紹及代碼示例,對正在學(xué)習(xí)java的小伙伴很有幫助,需要的朋友可以參考下
    2021-05-05
  • Java實現(xiàn)一個簡單的定時器代碼解析

    Java實現(xiàn)一個簡單的定時器代碼解析

    這篇文章主要介紹了Java實現(xiàn)一個簡單的定時器代碼解析,具有一定借鑒價值,需要的朋友可以參考下。
    2017-12-12
  • 一文解析Apache?Avro數(shù)據(jù)

    一文解析Apache?Avro數(shù)據(jù)

    本文是avro解析的demo,當(dāng)前FlinkSQL僅適用于簡單的avro數(shù)據(jù)解析,復(fù)雜嵌套avro數(shù)據(jù)暫時不支持。本文主要解析Apache?Avro數(shù)據(jù)的相關(guān)內(nèi)容,感興趣的朋友一起看看吧
    2021-12-12

最新評論