Java類隔離加載實現原理是什麼?

2021-01-09 黑馬程式設計師

Java類隔離加載實現原理是什麼? JVM 提供一個全局類加載器的設置接口,直接替換全局類加載器,但無法解決多個自定義類加載器同時存在的問題。然而JVM會選擇當前類的類加載器來加載所有該類的引用的類。

類隔離技術是什麼?

只要Java 代碼寫得足夠多就一定會出現這種情況:系統新引入了一個中間件的 jar 包,編譯的時候一切正常,一運行就報錯:java.lang.NoSuchMethodError,然後就哼哧哼哧的開始找解決方法,最後在幾百個依賴包裡面找的眼睛都快瞎了才找到衝突的 jar,把問題解決之後就開始吐槽中間件為啥搞那麼多不同版本的 jar,寫代碼五分鐘,排包排了一整天。

上面這種情況就是Java 開發過程中常見的情況,原因也很簡單,不同 jar 包依賴了某些通用 jar 包(如日誌組件)的版本不一樣,編譯的時候沒問題,到了運行時就會因為加載的類跟預期不符合導致報錯。

舉個例子:A 和 B 分別依賴了 C 的 v1 和 v2 版本,v2 版本的 Log 類比 v1 版本新增了 error 方法,現在工程裡面同時引入了 A、B 兩個 jar 包,以及 C 得 v0.1、v0.2 版本,打包的時候 maven 只能選擇一個 C 的版本,假設選擇了 v1 版本。到了運行的時候,默認情況下一個項目的所有類都是用同一個類加載器加載的,所以不管你依賴了多少個版本的 C,最終只會有一個版本的 C 被加載到 JVM 中。當 B 要去訪問 Log.error,就會發現 Log 壓根就沒有 error 方法,然後就拋異常java.lang.NoSuchMethodError。這就是類衝突的一個典型案例。

類衝突的問題如果版本是向下兼容的其實很好解決,把低版本的排除掉就完事了。但要是遇到版本不向下兼容的那就陷入了「救媽媽還是救女朋友」的兩難處境了。

為了避免兩難選擇,有人就提出了類隔離技術來解決類衝突的問題。類隔離的原理也很簡單,就是讓每個模塊使用獨立的類加載器來加載,這樣不同模塊之間的依賴就不會互相影響。如下圖所示,不同的模塊用不同的類加載器加載。

為什麼這樣做就能解決類衝突呢?這裡用到了 Java 的一個機制:不同類加載器加載的類在 JVM 看來是兩個不同的類,因為在 JVM 中一個類的唯一標誌是 類加載器+類名。通過這種方式我們就能夠同時加載 C 的兩個不同版本的類,即使它類名是一樣的。注意,這裡類加載器指的是類加載器的實例,並不是一定要定義兩個不同類加載器,例如圖中的 PluginClassLoaderA 和 PluginClassLoaderB 可以是同一個類加載器的不同實例。

實現類隔離的原理是什麼?

類隔離就是讓不同模塊的 jar 包用不同的類加載器加載,要做到這一點,就需要讓 JVM 能夠使用自定義的類加載器加載我們寫的類以及其關聯的類。

那麼如何實現呢?一個很簡單的做法就是 JVM 提供一個全局類加載器的設置接口,這樣我們直接替換全局類加載器就行了,但是這樣無法解決多個自定義類加載器同時存在的問題。

實際上 JVM 提供了一種非常簡單有效的方式,我把它稱為類加載傳導規則:JVM 會選擇當前類的類加載器來加載所有該類的引用的類。例如我們定義了 TestA 和 TestB 兩個類,TestA 會引用 TestB,只要我們使用自定義的類加載器加載 TestA,那麼在運行時,當 TestA 調用到 TestB 的時候,TestB 也會被 JVM 使用 TestA 的類加載器加載。

依此類推,只要是 TestA 及其引用類關聯的所有 jar 包的類都會被自定義類加載器加載。通過這種方式,我們只要讓模塊的 main 方法類使用不同的類加載器加載,那麼每個模塊的都會使用 main 方法類的類加載器加載的,這樣就能讓多個模塊分別使用不同類加載器。這也是 OSGi 和 SofaArk 能夠實現類隔離的核心原理。

了解類隔離的實現原理之後,從重寫類加載器開始進行實操。要實現類加載器,首先讓自定義的類加載器繼承 java.lang.ClassLoader,然後重寫類加載的方法,這裡有兩個選擇,一是重寫 findClass(String name),二是重寫 loadClass(String name)。

相關焦點

  • Java中加載資料庫驅動的方式有幾種?背後的原理是什麼?
    Driver接口先來了解下java.sql.Driver接口,java.sql.Driver是所有JDBC驅動程序需要實現的接口。這個接口是提供給資料庫廠商使用的,不同廠商實現該接口的類名是不同的,例如MySQL 8.x的JDBC驅動的類名是:com.mysql.cj.jdbc.Driver。
  • 2018年阿里巴巴關於Java重要開源項目匯總
    企業級流式計算引擎 JStormJStorm 是參考 Apache Storm 實現的實時流式計算框架,在網絡IO、線程模型、資源調度、可用性及穩定性上做了持續改進,已被越來越多企業使用。JStorm 可以看作是 storm 的 java 增強版本,除了內核用純java實現外,還包括了thrift、python、facet ui。
  • 什麼是JAVA反射機制,詳細解讀JAVA面試的核心技術
    總結就是:反射可以實現運行時知道任意一個類的屬性和方法。二、Java當中為什麼需要反射機制?工作原理是什麼?用在Java身上指的是運行時加載,探知,使用編譯期間未知的classes。下面探討一下反射是如何在僅知道類名的情況下能夠知道整個類的完整構造的(方法,屬性等)。首先我們都清楚,Java是一種解釋型的語言,即編譯器首先將源碼編譯成class文件,然後虛擬機(JVM)再將class文件解釋給目標計算機執行。
  • Java程式設計師必備基礎:Java代碼是怎麼運行的?
    java源文件編譯為class字節碼 類加載器把字節碼加載到虛擬機的方法區。 類加載 在Class文件中描述的各種信息,需要被加載到虛擬機之後才能運行和使用。因此,需要把class字節碼文件加載到Java虛擬機來。
  • Java經典面試題Spring是什麼 Spring框架入門詳解
    那麼Spring是什麼呢,Spring遵循分層的結構思想什麼什麼實現了高內聚低耦合巴拉巴拉一大堆,咬文嚼字不是我的強項,直接開幹,讓你們看看Spring到底是什麼東西。BeanFactory 是一種懶加載的方式,那麼當訪問的bean過多的時候伺服器壓力就變大了,所以beanFactory實際上是一種淘汰了的容器,而applicationContext更類似於一種緩存機制,所以它受眾更高,下面看一下列印結果可以看到User的toString方法已經打出,補充一下以上代碼我並沒有運行在java的main線程裡面,而是使用的junit
  • Java編程中基礎反射詳細解析
    有時會把這一整個流程統稱為類加載或類初始化。類加載指的是將類的class文件讀入內存中,並為之創建一個 java.lang.Class對象,也就是說程序使用任何類的時候,都會為其創建一個class對象。
  • 你知道java反射機制中class.forName和classloader的區別嗎?
    前兩天頭條有朋友留言說使用class.forName找不到類,可以使用classloader加載。趁此機會總結一下,正好看到面試中還經常問到。一、類加載機制上面兩種加載類的方式說到底還是為了加載一個java類,因此需要先對類加載的過程進行一個簡單的了解。
  • Java反射機制深入詳解
    Class 對象是在加載類時由 Java 虛擬機以及通過調用類加載器中的 defineClass 方法自動構造的。1 Person p1 = new Person(); 2 //下面的這三種方式都可以得到字節碼 3 CLass c1 = Date.class(); 4 p1.getClass(); 5 //若存在則加載,否則新建,往往使用第三種,類的名字在寫源程序時不需要知道,到運行時再傳遞過來 6 Class.forName("java.lang.String");CLass.forName
  • 徹底搞懂MyBatis插件原理及PageHelper原理
    MyBatis插件實現原理—目錄前言MyBatis中插件是如何實現的MyBatis插件的使用MyBatis插件實現原理插件的加載插件如何進行攔截攔截Executor對象其他對象插件解析插件執行流程假如一個對象被代理很多次PageHelper插件的使用PageHelper插件原理為什麼PageHelper只對startPage後的第一條select語句有效不通過插件能否改變
  • 反射——Java高級開發必須懂得
    靜態加載類:編譯時刻加載類(通過new關鍵字創建的對象)。動態加載類:運行時刻加載類(Class.forName(),得到類類型調用newInstance())。案例:區分編譯時刻加載類和運行時刻加載類(使用EditPlus工具,Eclipse表現不明顯)。
  • JAVA專業術語面試100問
    Error與Exception有什麼區別?17、java中的throw 和 throws關鍵字有什麼區別?18、列舉幾個你了解的幾個常見的運行時異常?19、final, finally, finalize有什麼區別?
  • Java反射初探 ——「當類也學會照鏡子」
    動態加載類 我理解的「反射」的意義(僅個人理解哈) 我理解的java反射機制就是: 提供一套完善而強大的API「反射「類的結構。,而getDeclaredMethods取得的method對應的方法不包括從父類中繼承的那一部分 例如上面通過列印getDeclaredMethods列印的MyClass的方法信息:getValuesetValue  讓我們看看通過getMethods列印又會取得什麼結果
  • Java反射是什麼?看這篇絕對會了!
    Map,只要實現了Map接口,就可以使用全類名路徑傳入到方法中,獲得對應的 Map 實例。「在之後的文章中會專門寫一篇文章講解如何利用反射實現一個簡易版的IOC容器,IOC容器原理很簡單,只要掌握了反射的思想,了解反射的常用 API 就可以實現,我可以提供一個簡單的思路:利用 HashMap 存儲所有實例,key 代表 <bean> 標籤的 id,value 存儲對應的實例,這對應了 Spring IOC容器管理的對象默認是單例
  • JAVA反序列化—FastJson抗爭的一生
    至此就完成了在知道Templates觸發類原理的情況下,變形衍生到了fastjson中完成RCE。這個過程會被叫做fastjson的反序列化過程,注意不要把這個過程跟java反序列化過程混為一談。它們兩個是同等級的存在,而不是前者基於後者之上。也就是說readObject反序列化利用點那一套在這根本不適用。相應的@type加載任意類+符合條件的setter與getter變成了反序列化利用點(個人總結的三要素中的反序列化漏洞觸發點)。
  • Java 反射:框架設計的靈魂
    比如 C 語言;Java 嚴格來說也是編譯型語言,但又介於編譯型和解釋型之間;Java 不直接生成機器碼而是生成中間碼:編譯期間,是將源碼交給編譯器生成 class 文件(字節碼),這個過程中只做了翻譯的工作,並沒有把代碼放入內存運行;當進入運行期,字節碼才被 Java 虛擬機加載、解釋成機器語言並運行。
  • smart-doc 1.9.7 發布,Java 零註解文檔生成工具
    ,smart-doc顛覆了傳統類似swagger這種大量採用註解侵入來生成文檔的實現方法。smart-doc完全基於接口源碼分析來生成接口文檔,完全做到零註解侵入,你只需要按照java標準注釋編寫,smart-doc就能幫你生成一個簡易明了的markdown 或是一個像GitBook樣式的靜態html文檔。如果你已經厭倦了swagger等文檔工具的無數註解和強侵入汙染,那請擁抱smart-doc吧!
  • Java NIO 基礎知識
    在 Java 領域,一般性的文件操作確實只需要和 java.io 包打交道就可以了,尤其對於寫業務代碼的程式設計師來說。不過,當你寫了兩三年代碼後,你的業務代碼可能已經寫得很溜了,蒙著眼睛也能寫增刪改查了。這個時候,也許你會想要開始了解更多的底層內容,包括並發、JVM、分布式系統、各個開源框架源碼實現等,處於這個階段的程式設計師會開始認識到 NIO 的用處,因為系統間通訊無處不在。
  • 面試官:給我手寫一個哈夫曼編碼(java語言實現)
    一、基本概念哈夫曼樹的目的是找出存放一串字符所需的最少的二進位編碼, 原理是通過統計出每種字符出現的頻率!不斷地對其合併。舉個例子:有一串字符,現在把這些字符進行統計,頻率表 A:60, B:45, C:13 D:69 E:14 F:5 G:3。現在要對這些字符進行編碼,但是前提是使用最少的二進位編碼。這時候怎麼辦呢?
  • webpack的異步加載原理及分包策略
    webpack 異步加載原理webpack ensure 有人稱它為異步加載,也有人稱為代碼切割,他其實就是將 js 模塊給獨立導出一個.js 文件,然後使用這個模塊的時候,再創建一個 script 對象,加入到 document.head 對象中,瀏覽器會自動幫我們發起請求,去請求這個
  • 「JAVA」萬字長篇詳述字節碼對象與反射機制完成動態編程
    Java 反射在Java的開發環境中,運行java文件需要使用:java xx.java 命令,運行java命令後,便會啟動JVM,將字節碼文件加載到JVM中,然後開始運行;當運行java命令時,該命令將會啟動一個JVM進程,在這個JVM進程中,會保存有該JVM創建的所有線程、變量、對象,這些線程、變量、對象會共享該JVM的內存區域。