溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

java雙親委派模型是什么

發(fā)布時間:2021-11-16 15:54:02 來源:億速云 閱讀:143 作者:iii 欄目:大數(shù)據(jù)

這篇文章主要介紹“java雙親委派模型是什么”,在日常操作中,相信很多人在java雙親委派模型是什么問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”java雙親委派模型是什么”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!

雙親委派模型

java雙親委派模型是什么

類加載器種類

從 Java 虛擬機的角度來說,只存在兩種不同的類加載器:一種是啟動類加載器(Bootstrap ClassLoader),這個類加載器使用 C++ 語言實現(xiàn)(HotSpot 虛擬機中),是虛擬機自身的一部分;另一種就是所有其他的類加載器,這些類加載器都有 Java 語言實現(xiàn),獨立于虛擬機外部,并且全部繼承自 java.lang.ClassLoader。

從開發(fā)者的角度,類加載器可以細分為:

啟動(Bootstrap)類加載器:負責將 Java_Home/lib 下面的類庫加載到內存中(比如 rt.jar)。由于引導類加載器涉及到虛擬機本地實現(xiàn)細節(jié),開發(fā)者無法直接獲取到啟動類加載器的引用,所以不允許直接通過引用進行操作。 標準擴展(Extension)類加載器:是由 Sun 的 ExtClassLoader(sun.misc.Launcher$ExtClassLoader) 實現(xiàn)的。它負責將 Java_Home /lib/ext 或者由系統(tǒng)變量 java.ext.dir 指定位置中的類庫加載到內存中。開發(fā)者可以直接使用標準擴展類加載器。 應用程序(Application)類加載器:是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader) 實現(xiàn)的。它負責將系統(tǒng)類路徑(CLASSPATH)中指定的類庫加載到內存中。開發(fā)者可以直接使用系統(tǒng)類加載器。由于這個類加載器是 ClassLoader 中的 getSystemClassLoader() 方法的返回值,因此一般稱為系統(tǒng)(System)加載器。 除此之外,還有自定義的類加載器,它們之間的層次關系被稱為類加載器的 雙親委派模型。該模型要求除了頂層的啟動類加載器外,其余的類加載器都應該有自己的父類加載器,而這種父子關系一般通過組合(Composition)關系來實現(xiàn),而不是通過繼承(Inheritance)。

雙親委派模型過程

public class XiongWeiVO extends XiongweiMother{}

某個特定的類比如XiongWeiVO的加載器在接到加載類的請求時,首先將加載任務委托給父類加載器比如XiongweiMother,依次遞歸,如果父類加載器可以完成類加載任務,就成功返回;只有父類加載器無法完成此加載任務時,才自己去加載。

使用雙親委派模型的好處在于 Java 類隨著它的類加載器一起具備了一種帶有==優(yōu)先級==的層次關系。例如類 java.lang.Object ,它存在在 rt.jar 中,無論哪一個類加載器要加載這個類,最終都是委派給處于模型最頂端的 ==Bootstrap ClassLoader== 進行加載,因此 Object 類在程序的各種類加載器環(huán)境中都是同一個類。

雙親委派模型的系統(tǒng)實現(xiàn)

在 java.lang.ClassLoader 的 loadClass() 方法中,先檢查是否已經被加載過,若沒有加載則調用父類加載器的 loadClass() 方法,若父加載器為空則默認使用啟動類加載器作為父加載器。如果父加載失敗,則拋出 ClassNotFoundException 異常后,再調用自己的 findClass() 方法進行加載。

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                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.
                    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;
        }
    }
    
    protected final Class<?> findLoadedClass(String name) {
        if (!checkName(name))
            return null;
        return findLoadedClass0(name);
    }
    
    protected Object getClassLoadingLock(String className) {
        Object lock = this;
        if (parallelLockMap != null) {
            Object newLock = new Object();
            lock = parallelLockMap.putIfAbsent(className, newLock);
            if (lock == null) {
                lock = newLock;
            }
        }
        return lock;
    }

注意,雙親委派模型是 Java 設計者推薦給開發(fā)者的類加載器的實現(xiàn)方式,并不是強制規(guī)定的。大多數(shù)的類加載器都遵循這個模型,但是 JDK 中也有較大規(guī)模破壞雙親模型的情況,例如線程上下文類加載器(Thread Context ClassLoader)的出現(xiàn)

ContextClassLoader

Thread.currentThread().getContextClassLoader();

從方法名字來看,應該是獲取當前上下文的類加載器

Thread.currentThread().getContextClassLoader();

this.getClass().getClassLoader();

這兩種方式都可以獲取Classload,有什么區(qū)別呢

雙親委派是單向的

Thread context class loader存在的目的主要是為了解決parent delegation機制下無法干凈的解決的問題。假如有下述委派鏈: ClassLoader A -> System class loader -> Extension class loader -> Bootstrap class loader 那么委派鏈左邊的ClassLoader就可以很自然的使用右邊的ClassLoader所加載的類。 但如果情況要反過來,是右邊的ClassLoader所加載的代碼需要反過來去找委派鏈靠左邊的ClassLoader去加載東西怎么辦呢?沒轍,parent delegation是單向的,沒辦法反過來從右邊找左邊.*

就是說當我們this.getClass().getClassLoader();可以獲取到所有已經加載過的文件, 但是System class loader -> Extension class loader -> Bootstrap class loader 就獲取不到ClassLoader A 能加載到的信息,那么怎么辦呢? 于是,Thread就把當前的類加載器,給保存下來了,其他加載器,需要的時候,就把當前線程的加載器,獲取到.

ContextClassLoader使用場景JNDI

JNDI(Java Naming and Directory Interface,Java命名和目錄接口)是SUN公司提供的一種標準的Java命名系統(tǒng)接口,JNDI提供統(tǒng)一的客戶端API,通過不同的訪問提供者接口 在系統(tǒng)中,要調用開發(fā)者的資源,此時就遇到了這種情況,JAX和rt.jar 因為是兩個加載器加載的,那么BootStrap需要加載Ext的資源,怎么辦? 這不是與委托機制相反了嗎? 所以就不能只依賴委派雙親模式,Thread.currentThread().getContextClassLoader()就出現(xiàn)了

案例

在spring源碼中經常能看到Thread.currentThread().getContextClassLoader()的使用場景,這里我們自己做一個配置文件加載工具類,來了解如何使用ContextClassLoader

package com.wuxiongwei.java.xml;

import org.springframework.util.ClassUtils;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;


public class ClassPathResource {
    private final String path;
    private ClassLoader classLoader;

    public ClassPathResource(String name) {
        this(name, getDefaultClassLoader());
    }

    public ClassPathResource(String name, ClassLoader classLoader) {
        this.path = name;
        this.classLoader = classLoader;
    }

    public static ClassLoader getDefaultClassLoader() {
        ClassLoader cl = null;

        try {
            cl = Thread.currentThread().getContextClassLoader();
        } catch (Throwable var3) {
            ;
        }

        if(cl == null) {
            cl = ClassUtils.class.getClassLoader();
            if(cl == null) {
                try {
                    cl = ClassLoader.getSystemClassLoader();
                } catch (Throwable var2) {
                    ;
                }
            }
        }

        return cl;
    }

    public InputStream getInputStream() {
        InputStream is;
        if (this.classLoader != null) {
            is = this.classLoader.getResourceAsStream(this.path);
        } else {//當還是加載不到,調用上層加載器
            is = ClassLoader.getSystemResourceAsStream(this.path);
        }

        if (is == null) {
            throw new RuntimeException(path + " cannot be opened because it does not exist");
        } else {
            return is;
        }
    }


    public String getResourceStreamAsString() {
        InputStream is = getInputStream();
        BufferedReader reader = new BufferedReader(new InputStreamReader(is));
        StringBuilder sb = new StringBuilder();
        String line = null;
        try {
            while ((line = reader.readLine()) != null) {
                sb.append(line + "\n");
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return sb.toString();
    }

    public static void main(String[] args) {
        ClassPathResource classPathResource = new ClassPathResource("logback-spring.xml");
        System.out.println(classPathResource.getResourceStreamAsString());
    }
}

如果了解了parent delegation和Thread context class loader之后,還是覺得不過癮的話呢,就把類加載的基礎知識也一起溫習一下吧。

類加載器

加載類的開放性

類加載器(ClassLoader)是 Java 語言的一項創(chuàng)新,也是 Java 流行的一個重要原因。在類加載的第一階段“加載”過程中,需要通過一個類的全限定名來獲取定義此類的二進制字節(jié)流,完成這個動作的代碼塊就是 類加載器。這一動作是放在 Java 虛擬機外部去實現(xiàn)的,以便讓應用程序自己決定如何獲取所需的類。

虛擬機規(guī)范并沒有指明二進制字節(jié)流要從一個 Class 文件獲取,或者說根本沒有指明從哪里獲取、怎樣獲取。這種開放使得 Java 在很多領域得到充分運用,例如:

從 ZIP 包中讀取,這很常見,成為 JAR,EAR,WAR 格式的基礎 從網(wǎng)絡中獲取,最典型的應用就是 Applet 運行時計算生成,最典型的是動態(tài)代理技術,在 java.lang.reflect.Proxy 中,就是用了 ProxyGenerator.generateProxyClass 來為特定接口生成形式為“*$Proxy”的代理類的二進制字節(jié)流 有其他文件生成,最典型的 JSP 應用,由 JSP 文件生成對應的 Class 類

類加載器與類的唯一性

類加載器雖然只用于實現(xiàn)類的加載動作,但是對于任意一個類,都需要由加載它的類加載器和這個類本身共同確立其在 Java 虛擬機中的 唯一性。通俗的說,JVM 中兩個類是否“相等”,首先就必須是同一個類加載器加載的,否則,即使這兩個類來源于同一個 Class 文件,被同一個虛擬機加載,只要類加載器不同,那么這兩個類必定是不相等的。

這里的“相等”,包括代表類的 Class 對象的 equals() 方法、isAssignableFrom() 方法、isInstance() 方法的返回結果,也包括使用 instanceof 關鍵字做對象所屬關系判定等情況。

下代碼說明了不同的類加載器對 instanceof 關鍵字運算的結果的影響。

package com.jvm.classloading;

import java.io.IOException;
import java.io.InputStream;

/**
 * 類加載器在類相等判斷中的影響
 * 
 * instanceof關鍵字
 * 
 */

public class ClassLoaderTest {
    public static void main(String[] args) throws Exception {
        // 自定義類加載器
        ClassLoader myLoader = new ClassLoader() {
            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException {
                try {
                    String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
                    InputStream is = getClass().getResourceAsStream(fileName);
                    if (is == null) {
                        return super.loadClass(fileName);
                    }
                    byte[] b = new byte[is.available()];
                    is.read(b);
                    return defineClass(name, b, 0, b.length);   
                } catch (IOException e) {
                    throw new ClassNotFoundException();
                }
            }
        };

        // 使用 ClassLoaderTest 的類加載器加載本類
        Object obj1 = ClassLoaderTest.class.getClassLoader().loadClass("com.jvm.classloading.ClassLoaderTest").newInstance();
        System.out.println(obj1.getClass());
        System.out.println(obj1 instanceof com.jvm.classloading.ClassLoaderTest);

        // 使用自定義類加載器加載本類
        Object obj2 = myLoader.loadClass("com.jvm.classloading.ClassLoaderTest").newInstance();
        System.out.println(obj2.getClass());
        System.out.println(obj2 instanceof com.jvm.classloading.ClassLoaderTest);
    }
}

輸出結果:

class com.jvm.classloading.ClassLoaderTest
true
class com.jvm.classloading.ClassLoaderTest
false

myLoader 是自定義的類加載器,可以用來加載與自己在同一路徑下的 Class 文件。main 函數(shù)的第一部分使用系統(tǒng)加載主類 ClassLoaderTest 的類加載器加載 ClassLoaderTest,輸出顯示,obj1 的所屬類型檢查正確,這是虛擬機中有 2 個 ClassLoaderTest 類,一個是主類,另一個是 main() 方法中加載的類,由于這兩個類使用同一個類加載器加載并且來源于同一個 Class 文件,因此這兩個類是完全相同的。

第二部分使用自定義的類加載器加載 ClassLoaderTest,class com.jvm.classloading.ClassLoaderTest 顯示,obj2 確實是類 com.jvm.classloading.ClassLoaderTest 實例化出來的對象,但是第二句輸出 false。此時虛擬機中有 3 個 ClassLoaderTest 類,由于第 3 個類的類加載器與前面 2 個類加載器不同,雖然來源于同一個 Class 文件,但它是一個獨立的類,所屬類型檢查是返回結果自然是 false。

到此,關于“java雙親委派模型是什么”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續(xù)學習更多相關知識,請繼續(xù)關注億速云網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經查實,將立刻刪除涉嫌侵權內容。

AI