1. 程式人生 > >說一說JVM雙親委派機制與Tomcat

說一說JVM雙親委派機制與Tomcat

講個故事:

以前,愛搗鼓的小明突然靈機一動,寫出了下面的程式碼

package java.lang;


public class String {
    //...複製真正String的其他方法
    
    public boolean equals(Object anObject) {
        sendEmail(xxx);
        return equalsReal(anObject);
    }
    
    //...
}

這樣,只要引用java.lang.String的人,小明能隨時收到他的系統的相關資訊,這簡直是個天才的注意。然而實施的時候卻發現,JVM並沒有載入這個類。

這是為什麼呢?

小明能想到的事情,JVM設計者也肯定能想到。

雙親委派模型

上述故事純屬瞎編,不過,這確實是以前JVM存在的一個問題,這幾天看Tomcat原始碼的時候,發現頻繁出現ClassLoader為什麼要用這個東西呢?

想要解答這個問題,得先了解一個定義:雙親委派模型。

這個詞第一次看見是在《深入理解JVM》中,目的也是為了解決上面所提出來的問題。

在JVM中,存在三種類型的類載入器:

  • 啟動類(Bootstrap)載入器: 用於載入本地(Navicat)程式碼類的載入器,它負責裝入%JAVA_HOME%/lib下面的類。由於引導類載入器涉及到虛擬機器本地實現細節,開發者無法直接獲取到啟動類載入器的引用,所以不允許直接通過引用進行操作。
  • 標準擴充套件(Extension)類載入器: 由ExtClassLoader實現,負責載入%JAVA_HOME/lib/ext%或者系統變數java.ext.dir(可使用System.out.println("java.ext.dir")檢視)指定的類載入到記憶體中
  • 系統(System)類載入器: 由AppClassLoader實現,負責載入系統類(環境變數%CLASSPATH%)指定,預設為當前路徑的類載入到記憶體中。

除去以上三種外,還有一種比較特殊的執行緒上下文類載入器。存在於Thread類中,一般使用方式為new Thread().getContextClassLoader()

可以看出來,三種類型的載入器負責不同的模組的載入。那怎麼才能保證我所使用的String

就是JDK裡面的String呢?這就是雙親委派模型的功能了:

上面三種類載入器中,他們之間的關係為:

也就是Bootstrap ClassLoader作為Extension ClassLoader的父類,而Extension ClassLoader作為Application ClassLoader的父類,Application ClassLoader是作為User ClassLoader的父類的。

而雙親委派機制規定:當某個特定的類載入在接收到類載入的請求的時候,首先需要將載入任務委託給父類載入器,依次遞迴到頂層後,如果最高層父類能夠找到需要載入的類,則成功返回,若父類無法找到相關的類,則依次傳遞給子類。

補充:

  • 如果A類引用了B,則JVM將使用載入類A的載入器載入類B
  • 類載入器存在快取,如果某個載入器以前成功載入過某個類後,再次接受到此類載入請求則直接返回,不再向上傳遞載入請求
  • 可以通過ClassLoader.loadClass()Class.ForName(xxx,true,classLoader)指定某個載入器載入類
  • 類型別由載入它的載入器和這個類本身共同決定,如果類載入器不同,類名相同,instanceof依然會返回false
  • 父載入器無法載入子載入器能夠載入的類

可以看到,通過雙親委派機制,能夠保證使用的類的安全性,並且可以避免類重名的情況下JVM存在多個相同的類名相同,位元組碼不同的類。

回到剛開始講的故事,雖然小明自定義了String,包名也叫java.lang,但是當用戶使用String的時候,會由普通的Application ClassLoader載入java.lang.String,此時通過雙親委派,類載入請求會上傳給Application ClassLoader的父類,直到傳遞給Bootstrap ClassLoader,而此時,Bootstrap ClassLoader將在%JAVA_HOME%/lib中尋找java.lang.String而此時正好能夠找到java.lang.String,載入成功,返回。因此小明自己寫的java.lang.String並沒有被載入。

可以看見,如果真的想要實現小明的計劃,只能將小明自己編寫的java.lang.String這個class檔案替換到%JAVA_HOME%/lib/rt.jar 中的String.class


自定義ClassLoader

到這裡,估計能明白為什麼需要雙親委派模型了,而某些時候,我們可以看見許多框架都自定義了ClassLoader,通過自定義ClassLoader,我們可以做很多好玩的事情,比如:設計一個從指定路徑動態載入類的類載入器:

public class DiskClassLoader extends  ClassLoader {

    private String libPath;

    public DiskClassLoader(String path){
        libPath=path;
    }


    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try(FileInputStream fileInputStream=new FileInputStream(new File(libPath,getFileName(name)));
            BufferedInputStream bufferedInputStream=new BufferedInputStream(fileInputStream);
            ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream()){

                    for (int len=0;(len=bufferedInputStream.read())!=-1;){
                    byteArrayOutputStream.write(len);
                }

            byte[] data=byteArrayOutputStream.toByteArray();
            return defineClass(name,data,0,data.length);
        }catch (IOException e){
            e.printStackTrace();
        }
        return super.findClass(name);
    }

    private String getFileName(String name) {
        int index = name.lastIndexOf('.');
        if(index == -1){
            return name+".class";
        }else{
            return name.substring(index+1)+".class";
        }
    }
}

上面是一個簡單的例子,可以看見想要自定義ClassLoader,只需要繼承ClassLoader,然後覆蓋findClass()方法即可,其中findClass()是負責獲取指定類的位元組碼的,在獲取到位元組碼後,需要手動呼叫defineClass()載入類。

ClassLoader類中,我們能找到loadClass的原始碼:

 protected Class<?> loadClass(String name, boolean resolve)       {
    
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {              
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }         

                if (c == null) {
                    
                    c = findClass(name);                 
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;   
    }

在刪減掉一些模板程式碼後,我們可以看到loadClass()方法就是實現雙親委派的主要程式碼:首先檢視類是否有快取,如果沒有,就呼叫父類的loadClass方法,讓父類去載入,如果父類載入失敗,則自己載入,如果自己載入失敗,那就返回null,注意:並沒有再找自己的子類去尋找類,也就是在哪裡發起的載入,就在哪裡結束。

這裡可以看到,loadClass()方法並沒有被標記為final的,也就是我們依然可以過載它的loadClass()方法,破壞原本的委派雙親模型。

破壞雙親委派機制

有些時候,雙親委派機制也會遇到一些問題,在介紹雙親委派機制的時候,我列舉了一些補充。而在一些JDK中,存在一些基礎API他們的載入由比較上層的載入器負責,這些API只是一些簡單的介面,而具體的實現可能會由其他使用者自己實現,這個時候就存在一個問題,如果這些基礎的API需要呼叫/載入使用者的程式碼的時候,會發現由於父類無法找到子類所能載入的類的原因,呼叫失敗。

最典型的例子便是JNDI服務,JNDI服務是在JDK1.3的時候放入rt.jar中,而rt.jarBootstrap ClassLoader載入,JNDI的功能是對資源進行集中管理和查詢,它需要呼叫獨立廠商實現部部署在應用程式的classpath下的JNDI介面提供者(SPI, Service Provider Interface)的程式碼,但啟動類載入器不可能“認識”之些程式碼,該怎麼辦?

這就需要用到最開始講的特殊的載入器:上下文類載入器

上下文類載入器的使用方式為:Thread.currentThread().getContextClassLoader()

上下文類載入器是什麼意思呢?可以看原始碼,Thread初始化是通過本地方法currentThread();初始化的,而classLoader也正是通過currentThread初始化,currentThread指的是當前正在執行的執行緒。

而預設情況下,啟動Launcher後,Launcher會將當前執行緒的上下文載入器設定為Application ClassLoader

public Launcher() {
    //...
    try {
        this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
    } catch (IOException var9) {
        throw new InternalError("Could not create application class loader", var9);
    }
    Thread.currentThread().setContextClassLoader(this.loader);
    //...
}

因此,上下文類載入器預設就是系統載入器,通過上下文載入器,更高級別的載入器便可以呼叫系統載入器載入一個類。

Tomcat 與類載入器

Tomcat作為一個Web容器,會包含各種Web應用程式,而為了使各個應用程式不互相干擾,至少需要達到以下要求:

  • 部署在同一個Web容器上的兩個Web應用程式所使用的Java類庫可以實現相互隔離
  • 部署在同一個Web容器上的兩個Web應用程式所使用的Java類庫可以相互共享
  • Web容器需要保證自身的安全不受Web應用程式所影響
  • 只是JSP的容器,需要支援熱部署功能

因為這些需求,所以在Tomcat中,類的載入不能使用簡單的ClassLoader來載入,而是需要自定義分級的ClassLoader

在Tomcat中,定義了3組目錄結構/common/*,/server/*/shared/*可以存放Java類庫,另外還有Web應用程式自身的結構:/WEB-INF/*,而這幾級目錄結構分別對應了不同的載入器

  • common: 類庫可以被Tomcat和所有Web應用程式共同使用
  • server: 類庫可以被Tomcat使用,對其他Web程式不可見
  • shared: 類庫可以被所有的Web應用程式共同使用,但對Tomcat不可見
  • WEB-INF: 類庫僅僅能被自身Web應用程式使用

因此,需要支援以上結構,可以通過自定義遵循雙親委派模型的ClassLoader來完成。

參考連結:

一看你就懂,超詳細java中的ClassLoader詳解

Java類載入機制與Tomcat類載入器架構

【JVM】淺談雙親委派和破壞雙親委派

關於Java類載入雙親委派機制的思考


如果覺得寫得不錯,歡迎關注微信公眾號:逸遊Java ,每天不定時釋出一些有關Java乾貨的文章,感謝關注

相關推薦

JVM雙親委派機制Tomcat

講個故事: 以前,愛搗鼓的小明突然靈機一動,寫出了下面的程式碼 package java.lang; public class String { //...複製真正String的其他方法 public boolean equals(Object anObject) {

深入JVM系列(三)之類載入、類載入器、雙親委派機制常見問題

一.概述 定義:虛擬機器把描述類的資料從Class檔案載入到記憶體,並對資料進行校驗、轉換解析和初始化,最終形成可以被虛擬機器直接使用的java型別。類載入和連線的過程都是在執行期間完成的。 二.

深入探究JVM之類載入雙親委派機制

@[toc] # 前言 前面學習了虛擬機器的記憶體結構、物件的分配和建立,但物件所對應的類是怎麼載入到虛擬機器中來的呢?載入過程中需要做些什麼?什麼是雙親委派機制以及為什麼要打破雙親委派機制? # 類的生命週期 ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/2020073

JVM類載入雙親委派機制被打破

## 前言 前文已經講了虛擬機器將java檔案編譯成class檔案後的格式:**[JVM虛擬機器Class類檔案研究分析](https://www.cnblogs.com/Courage129/p/14358024.html)** java檔案經過編譯,形成class檔案,那麼虛擬機器如何將這些Class

違反ClassLoader雙親委派機制三部曲第二部——Tomcat類載入機制

前言: 本文是基於 ClassLoader雙親委派機制原始碼分析 瞭解過正統JDK類載入機制及其實現原理的基礎上,進而分析這種思想如何應用到Tomcat這個web容器中,從原始碼的角度對 違反ClassLoader雙親委派機制三部曲之首部——JDBC驅動載入 中提出的To

JVM(三、雙親委派機制1)

pan sent adc urn requested turn returns spa true 1.獲取ClassLoader 1 public class MyTest06 { 2 3 public static void main(Strin

關於Java類加載雙親委派機制的思考(附一道面試題)

另類 app 類庫 .com 任務 發現 clas context 表示 預定義類加載器和雙親委派機制 JVM預定義的三種類型類加載器: 啟動(Bootstrap)類加載器:是用本地代碼實現的類裝入器,它負責將 <Java_Runtime_Home>/l

java的類加載器體系結構和雙親委派機制

答案 類加載器 父類 編譯 自己 體系 文件加載 ext 類名 類加載器將字節碼文件加載到內存中,同時在方法區中生成對應的java.land.class對象 作為外部訪問方法區的入口。 類加載器的層次結構:            引導類加載器《-------------擴

JVM 垃圾回收機制GC效能調優

一、GC概要: JVM堆相關知識     為什麼先說JVM堆?     JVM的堆是Java物件的活動空間,程式中的類的物件從中分配空間,其儲存著正在執行著的應用程式用到的所有物件。這些物件的建立方式就是那些new一類的操作,當物件

雙親委派策略自定義類加載器

ext class對象 環境 etc UNC 雙親委派模型 根據 動作 extends 類加載器 類加載器(class loader)用來加載 Java 類到 Java 虛擬機中。一般來說,Java 虛擬機使用 Java 類的方式如下:Java 源程序(.java 文件)在

java類載入過程以及雙親委派機制

前言:最近兩個月公司實行了996上班制,加上了熬了兩個通宵上線,狀態很不好,頭疼、牙疼,一直沒有時間和精力寫部落格,也害怕在這樣的狀態下寫出來的東西出錯。為了不讓自己荒廢學習的勁頭和習慣,今天週日,也打算寫一篇部落格,就算是為了給自己以前立的flag(每個月必須寫幾篇部落格)的實現。那麼本次部落格的主題我選擇

ClassLoader和雙親委派機制

博文主要講classloader的模型、作用和使用,內容是作者學習java反射機制有關知識時記錄的筆記。 ClassLoader ClassLoad:類載入器(class loader)用來載入 Java 類到 Java 虛擬機器中。Java 源程式(.

[五]類載入機制雙親委派機制 底層程式碼實現原理 原始碼分析 java類載入雙親委派機制是如何實現的

Launcher啟動類 本文是雙親委派機制的原始碼分析部分,類載入機制中的雙親委派模型對於jvm的穩定執行是非常重要的 不過原始碼其實比較簡單,接下來簡單介紹一下 我們先從啟動類說起 有一個Launcher類   sun.misc.Launcher; 仔細看下這簡

分析JVM雙親委派模型的類載入原始碼 自定義類載入器

雙親委派模型下,在父類載入器無法載入的情況下再由當前類載入器去載入。具體的實現邏輯在java.util.ClassLoader抽象類的loadClass方法中。在該方法中,先檢查是否已經載入過,如果沒有,就讓父類載入器載入。如果父類載入器服務載入,就呼叫findClass方法載入。findC

圖解JVM類載入機制類載入過程

0、前言 讀完本文,你將瞭解到: 一、為什麼說Jabalpur語言是跨平臺的 二、Java虛擬機器啟動、載入類過程分析 三、類載入器有哪些?其組織結構是怎樣的? 四、雙親載入模型的邏輯和底層程式碼實現是怎樣的? 五、類載入器與Class<T&

雙親委派機制

1、當AppClassLoader載入一個class時,它首先不會自己去嘗試載入這個類,而是把類載入請求委派給父類載入器ExtCl

為什麼說JDBC驅動類載入破壞了雙親委派機制

大家都知道jdk的類載入機制是雙親委派機制,當我們需要載入一個類的時候,比如如下幾種情況 new一個類的物件 呼叫類的靜態成員(

Java虛擬機器類載入器及雙親委派機制

所謂的類載入器(Class Loader)就是載入Java類到Java虛擬機器中的,前面《面試官,不要再問我“Java虛擬機器類載入機制”了》中已經介紹了具體載入class檔案的機制。本篇文章我們重點介紹載入器和雙親委派機制。 類載入器 在JVM中有三類ClassLoader構成:啟動類(或根類)載入器(Bo

Java虛擬機器詳解(十)------雙親委派模型

  在上一篇部落格,我們介紹了類載入過程,包括5個階段,分別是“載入”,“驗證”,“準備”,“解析”,“初始化”,如下圖所示:        本篇部落格,我們來介紹Java虛

Java類載入器和雙親委派機制

前言 之前詳細介紹了Java類的整個載入過程(類載入機制詳解)。雖然,篇幅較長,但是也不要被內容嚇到了,其實每個階段都可以用一句話來概括。 1)載入:查詢並載入類的二進位制位元組流資料。 2)驗證:保證被載入的類的正確性。 3)準備:為類的靜態變數分配記憶體,並設定預設初始值。 4)解析:把類中的符號引用轉換