成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Java的神秘世界:為何說ClassLoader 是 Java最神秘的技術(shù)之一

開發(fā) 后端
本文我?guī)ёx者徹底吃透 ClassLoader,以后其它的相關(guān)文章你們可以不必再細看了。

 ClassLoader 是 Java 屆最為神秘的技術(shù)之一,無數(shù)人被它傷透了腦筋,摸不清門道究竟在哪里。網(wǎng)上的文章也是一篇又一篇,經(jīng)過本人的親自鑒定,絕大部分內(nèi)容都是在誤導(dǎo)別人。本文我?guī)ёx者徹底吃透 ClassLoader,以后其它的相關(guān)文章你們可以不必再細看了。

[[278790]]

ClassLoader 做什么的?

顧名思義,它是用來加載 Class 的。它負責(zé)將 Class 的字節(jié)碼形式轉(zhuǎn)換成內(nèi)存形式的 Class 對象。字節(jié)碼可以來自于磁盤文件 *.class,也可以是 jar 包里的 *.class,也可以來自遠程服務(wù)器提供的字節(jié)流,字節(jié)碼的本質(zhì)就是一個字節(jié)數(shù)組 []byte,它有特定的復(fù)雜的內(nèi)部格式。

 

Java的神秘世界:為何說ClassLoader 是 Java最神秘的技術(shù)之一

 

有很多字節(jié)碼加密技術(shù)就是依靠定制 ClassLoader 來實現(xiàn)的。先使用工具對字節(jié)碼文件進行加密,運行時使用定制的 ClassLoader 先解密文件內(nèi)容再加載這些解密后的字節(jié)碼。每個 Class 對象的內(nèi)部都有一個 classLoader 字段來標(biāo)識自己是由哪個 ClassLoader 加載的。ClassLoader 就像一個容器,里面裝了很多已經(jīng)加載的 Class 對象。

  1. class Class<T> { 
  2.  ... 
  3.  private final ClassLoader classLoader; 
  4.  ... 

延遲加載

JVM 運行并不是一次性加載所需要的全部類的,它是按需加載,也就是延遲加載。程序在運行的過程中會逐漸遇到很多不認識的新類,這時候就會調(diào)用 ClassLoader 來加載這些類。加載完成后就會將 Class 對象存在 ClassLoader 里面,下次就不需要重新加載了。

比如你在調(diào)用某個類的靜態(tài)方法時,首先這個類肯定是需要被加載的,但是并不會觸及這個類的實例字段,那么實例字段的類別 Class 就可以暫時不必去加載,但是它可能會加載靜態(tài)字段相關(guān)的類別,因為靜態(tài)方法會訪問靜態(tài)字段。而實例字段的類別需要等到你實例化對象的時候才可能會加載。

各司其職

JVM 運行實例中會存在多個 ClassLoader,不同的 ClassLoader 會從不同的地方加載字節(jié)碼文件。它可以從不同的文件目錄加載,也可以從不同的 jar 文件中加載,也可以從網(wǎng)絡(luò)上不同的靜態(tài)文件服務(wù)器來下載字節(jié)碼再加載。

JVM 中內(nèi)置了三個重要的 ClassLoader,分別是 BootstrapClassLoader、ExtensionClassLoader 和 AppClassLoader。

BootstrapClassLoader 負責(zé)加載 JVM 運行時核心類,這些類位于 $JAVA_HOME/lib/rt.jar 文件中,我們常用內(nèi)置庫 java.xxx.* 都在里面,比如 java.util.、java.io.、java.nio.、java.lang. 等等。這個 ClassLoader 比較特殊,它是由 C 代碼實現(xiàn)的,我們將它稱之為「根加載器」。

ExtensionClassLoader 負責(zé)加載 JVM 擴展類,比如 swing 系列、內(nèi)置的 js 引擎、xml 解析器 等等,這些庫名通常以 javax 開頭,它們的 jar 包位于 $JAVA_HOME/lib/ext/*.jar 中,有很多 jar 包。

AppClassLoader 才是直接面向我們用戶的加載器,它會加載 Classpath 環(huán)境變量里定義的路徑中的 jar 包和目錄。我們自己編寫的代碼以及使用的第三方 jar 包通常都是由它來加載的。

那些位于網(wǎng)絡(luò)上靜態(tài)文件服務(wù)器提供的 jar 包和 class文件,jdk 內(nèi)置了一個 URLClassLoader,用戶只需要傳遞規(guī)范的網(wǎng)絡(luò)路徑給構(gòu)造器,就可以使用 URLClassLoader 來加載遠程類庫了。URLClassLoader 不但可以加載遠程類庫,還可以加載本地路徑的類庫,取決于構(gòu)造器中不同的地址形式。ExtensionClassLoader 和 AppClassLoader 都是 URLClassLoader 的子類,它們都是從本地文件系統(tǒng)里加載類庫。

AppClassLoader 可以由 ClassLoader 類提供的靜態(tài)方法 getSystemClassLoader() 得到,它就是我們所說的「系統(tǒng)類加載器」,我們用戶平時編寫的類代碼通常都是由它加載的。當(dāng)我們的 main 方法執(zhí)行的時候,這第一個用戶類的加載器就是 AppClassLoader。

ClassLoader 傳遞性

程序在運行過程中,遇到了一個未知的類,它會選擇哪個 ClassLoader 來加載它呢?虛擬機的策略是使用調(diào)用者 Class 對象的 ClassLoader 來加載當(dāng)前未知的類。何為調(diào)用者 Class 對象?就是在遇到這個未知的類時,虛擬機肯定正在運行一個方法調(diào)用(靜態(tài)方法或者實例方法),這個方法掛在哪個類上面,那這個類就是調(diào)用者 Class 對象。前面我們提到每個 Class 對象里面都有一個 classLoader 屬性記錄了當(dāng)前的類是由誰來加載的。

因為 ClassLoader 的傳遞性,所有延遲加載的類都會由初始調(diào)用 main 方法的這個 ClassLoader 全全負責(zé),它就是 AppClassLoader。

雙親委派

前面我們提到 AppClassLoader 只負責(zé)加載 Classpath 下面的類庫,如果遇到?jīng)]有加載的系統(tǒng)類庫怎么辦,AppClassLoader 必須將系統(tǒng)類庫的加載工作交給 BootstrapClassLoader 和 ExtensionClassLoader 來做,這就是我們常說的「雙親委派」。

 

Java的神秘世界:為何說ClassLoader 是 Java最神秘的技術(shù)之一

 

AppClassLoader 在加載一個未知的類名時,它并不是立即去搜尋 Classpath,它會首先將這個類名稱交給 ExtensionClassLoader 來加載,如果 ExtensionClassLoader 可以加載,那么 AppClassLoader 就不用麻煩了。否則它就會搜索 Classpath 。

而 ExtensionClassLoader 在加載一個未知的類名時,它也并不是立即搜尋 ext 路徑,它會首先將類名稱交給 BootstrapClassLoader 來加載,如果 BootstrapClassLoader 可以加載,那么 ExtensionClassLoader 也就不用麻煩了。否則它就會搜索 ext 路徑下的 jar 包。

這三個 ClassLoader 之間形成了級聯(lián)的父子關(guān)系,每個 ClassLoader 都很懶,盡量把工作交給父親做,父親干不了了自己才會干。每個 ClassLoader 對象內(nèi)部都會有一個 parent 屬性指向它的父加載器。

  1. class ClassLoader { 
  2.  ... 
  3.  private final ClassLoader parent; 
  4.  ... 

值得注意的是圖中的 ExtensionClassLoader 的 parent 指針畫了虛線,這是因為它的 parent 的值是 null,當(dāng) parent 字段是 null 時就表示它的父加載器是「根加載器」。如果某個 Class 對象的 classLoader 屬性值是 null,那么就表示這個類也是「根加載器」加載的。注意這里的 parent 不是 super 不是父類,只是 ClassLoader 內(nèi)部的字段。

Class.forName

當(dāng)我們在使用 jdbc 驅(qū)動時,經(jīng)常會使用 Class.forName 方法來動態(tài)加載驅(qū)動類。

  1. Class.forName("com.mysql.cj.jdbc.Driver"); 

其原理是 mysql 驅(qū)動的 Driver 類里有一個靜態(tài)代碼塊,它會在 Driver 類被加載的時候執(zhí)行。這個靜態(tài)代碼塊會將 mysql 驅(qū)動實例注冊到全局的 jdbc 驅(qū)動管理器里。

  1. class Driver { 
  2.  static { 
  3.  try { 
  4.  java.sql.DriverManager.registerDriver(new Driver()); 
  5.  } catch (SQLException E) { 
  6.  throw new RuntimeException("Can't register driver!"); 
  7.  } 
  8.  } 
  9.  ... 

forName 方法同樣也是使用調(diào)用者 Class 對象的 ClassLoader 來加載目標(biāo)類。不過 forName 還提供了多參數(shù)版本,可以指定使用哪個 ClassLoader 來加載

  1. Class<?> forName(String name, boolean initialize, ClassLoader cl) 

通過這種形式的 forName 方法可以突破內(nèi)置加載器的限制,通過使用自定類加載器允許我們自由加載其它任意來源的類庫。根據(jù) ClassLoader 的傳遞性,目標(biāo)類庫傳遞引用到的其它類庫也將會使用自定義加載器加載。

自定義加載器

ClassLoader 里面有三個重要的方法 loadClass()、findClass() 和 defineClass()。

loadClass() 方法是加載目標(biāo)類的入口,它首先會查找當(dāng)前 ClassLoader 以及它的雙親里面是否已經(jīng)加載了目標(biāo)類,如果沒有找到就會讓雙親嘗試加載,如果雙親都加載不了,就會調(diào)用 findClass() 讓自定義加載器自己來加載目標(biāo)類。ClassLoader 的 findClass() 方法是需要子類來覆蓋的,不同的加載器將使用不同的邏輯來獲取目標(biāo)類的字節(jié)碼。拿到這個字節(jié)碼之后再調(diào)用 defineClass() 方法將字節(jié)碼轉(zhuǎn)換成 Class 對象。下面我使用偽代碼表示一下基本過程

  1. class ClassLoader { 
  2.  // 加載入口,定義了雙親委派規(guī)則 
  3.  Class loadClass(String name) { 
  4.  // 是否已經(jīng)加載了 
  5.  Class t = this.findFromLoaded(name); 
  6.  if(t == null) { 
  7.  // 交給雙親 
  8.  t = this.parent.loadClass(name
  9.  } 
  10.  if(t == null) { 
  11.  // 雙親都不行,只能靠自己了 
  12.  t = this.findClass(name); 
  13.  } 
  14.  return t; 
  15.  } 
  16.   
  17.  // 交給子類自己去實現(xiàn) 
  18.  Class findClass(String name) { 
  19.  throw ClassNotFoundException(); 
  20.  } 
  21.   
  22.  // 組裝Class對象 
  23.  Class defineClass(byte[] code, String name) { 
  24.  return buildClassFromCode(code, name); 
  25.  } 
  26. class CustomClassLoader extends ClassLoader { 
  27.  Class findClass(String name) { 
  28.  // 尋找字節(jié)碼 
  29.  byte[] code = findCodeFromSomewhere(name); 
  30.  // 組裝Class對象 
  31.  return this.defineClass(code, name); 
  32.  } 

自定義類加載器不易破壞雙親委派規(guī)則,不要輕易覆蓋 loadClass 方法。否則可能會導(dǎo)致自定義加載器無法加載內(nèi)置的核心類庫。在使用自定義加載器時,要明確好它的父加載器是誰,將父加載器通過子類的構(gòu)造器傳入。如果父類加載器是 null,那就表示父加載器是「根加載器」。

  1. // ClassLoader 構(gòu)造器 
  2. protected ClassLoader(String name, ClassLoader parent); 

雙親委派規(guī)則可能會變成三親委派,四親委派,取決于你使用的父加載器是誰,它會一直遞歸委派到根加載器。

Class.forName vs ClassLoader.loadClass

這兩個方法都可以用來加載目標(biāo)類,它們之間有一個小小的區(qū)別,那就是 Class.forName() 方法可以獲取原生類型的 Class,而 ClassLoader.loadClass() 則會報錯。

  1. Class<?> x = Class.forName("[I"); 
  2. System.out.println(x); 
  3. x = ClassLoader.getSystemClassLoader().loadClass("[I"); 
  4. System.out.println(x); 
  5. --------------------- 
  6. class [I 
  7. Exception in thread "main" java.lang.ClassNotFoundException: [I 
  8. ... 

項目管理上有一個著名的概念叫著「鉆石依賴」,是指軟件依賴導(dǎo)致同一個軟件包的兩個版本需要共存而不能沖突。

 

Java的神秘世界:為何說ClassLoader 是 Java最神秘的技術(shù)之一

 

我們平時使用的 maven 是這樣解決鉆石依賴的,它會從多個沖突的版本中選擇一個來使用,如果不同的版本之間兼容性很糟糕,那么程序?qū)o法正常編譯運行。Maven 這種形式叫「扁平化」依賴管理。使用 ClassLoader 可以解決鉆石依賴問題。不同版本的軟件包使用不同的 ClassLoader 來加載,位于不同 ClassLoader 中名稱一樣的類實際上是不同的類。下面讓我們使用 URLClassLoader 來嘗試一個簡單的例子,它默認的父加載器是 AppClassLoader

  1. $ cat ~/source/jcl/v1/Dep.java 
  2. public class Dep { 
  3.     public void print() { 
  4.         System.out.println("v1"); 
  5.     } 
  6. $ cat ~/source/jcl/v2/Dep.java 
  7. public class Dep { 
  8.  public void print() { 
  9.  System.out.println("v1"); 
  10.  } 
  11. $ cat ~/source/jcl/Test.java 
  12. public class Test { 
  13.     public static void main(String[] args) throws Exception { 
  14.         String v1dir = "file:///Users/qianwp/source/jcl/v1/"
  15.         String v2dir = "file:///Users/qianwp/source/jcl/v2/"
  16.         URLClassLoader v1 = new URLClassLoader(new URL[]{new URL(v1dir)}); 
  17.         URLClassLoader v2 = new URLClassLoader(new URL[]{new URL(v2dir)}); 
  18.          
  19.  Class<?> depv1Class = v1.loadClass("Dep"); 
  20.         Object depv1 = depv1Class.getConstructor().newInstance(); 
  21.         depv1Class.getMethod("print").invoke(depv1); 
  22.         Class<?> depv2Class = v2.loadClass("Dep"); 
  23.         Object depv2 = depv2Class.getConstructor().newInstance(); 
  24.         depv2Class.getMethod("print").invoke(depv2); 
  25.       
  26.  System.out.println(depv1Class.equals(depv2Class)); 
  27.  } 

在運行之前,我們需要對依賴的類庫進行編譯

  1. $ cd ~/source/jcl/v1 
  2. $ javac Dep.java 
  3. $ cd ~/source/jcl/v2 
  4. $ javac Dep.java 
  5. $ cd ~/source/jcl 
  6. $ javac Test.java 
  7. $ java Test 
  8. v1 
  9. v2 
  10. false 

在這個例子中如果兩個 URLClassLoader 指向的路徑是一樣的,下面這個表達式還是 false,因為即使是同樣的字節(jié)碼用不同的 ClassLoader 加載出來的類都不能算同一個類

  1. depv1Class.equals(depv2Class) 

我們還可以讓兩個不同版本的 Dep 類實現(xiàn)同一個接口,這樣可以避免使用反射的方式來調(diào)用 Dep 類里面的方法。

  1. Class<?> depv1Class = v1.loadClass("Dep"); 
  2. IPrint depv1 = (IPrint)depv1Class.getConstructor().newInstance(); 
  3. depv1.print() 

ClassLoader 固然可以解決依賴沖突問題,不過它也限制了不同軟件包的操作界面必須使用反射或接口的方式進行動態(tài)調(diào)用。Maven 沒有這種限制,它依賴于虛擬機的默認懶惰加載策略,運行過程中如果沒有顯示使用定制的 ClassLoader,那么從頭到尾都是在使用 AppClassLoader,而不同版本的同名類必須使用不同的 ClassLoader 加載,所以 Maven 不能完美解決鉆石依賴。 如果你想知道有沒有開源的包管理工具可以解決鉆石依賴的,我推薦你了解一下 sofa-ark,它是螞蟻金服開源的輕量級類隔離框架。

分工與合作

這里我們重新理解一下 ClassLoader 的意義,它相當(dāng)于類的命名空間,起到了類隔離的作用。位于同一個 ClassLoader 里面的類名是唯一的,不同的 ClassLoader 可以持有同名的類。ClassLoader 是類名稱的容器,是類的沙箱。

 

Java的神秘世界:為何說ClassLoader 是 Java最神秘的技術(shù)之一

 

不同的 ClassLoader 之間也會有合作,它們之間的合作是通過 parent 屬性和雙親委派機制來完成的。parent 具有更高的加載優(yōu)先級。除此之外,parent 還表達了一種共享關(guān)系,當(dāng)多個子 ClassLoader 共享同一個 parent 時,那么這個 parent 里面包含的類可以認為是所有子 ClassLoader 共享的。這也是為什么 BootstrapClassLoader 被所有的類加載器視為祖先加載器,JVM 核心類庫自然應(yīng)該被共享。Thread.contextClassLoader

如果你稍微閱讀過 Thread 的源代碼,你會在它的實例字段中發(fā)現(xiàn)有一個字段非常特別

  1. class Thread { 
  2.  ... 
  3.  private ClassLoader contextClassLoader; 
  4.   
  5.  public ClassLoader getContextClassLoader() { 
  6.  return contextClassLoader; 
  7.  } 
  8.   
  9.  public void setContextClassLoader(ClassLoader cl) { 
  10.  this.contextClassLoader = cl; 
  11.  } 
  12.  ... 

contextClassLoader「線程上下文類加載器」,這究竟是什么東西?

首先 contextClassLoader 是那種需要顯示使用的類加載器,如果你沒有顯示使用它,也就永遠不會在任何地方用到它。你可以使用下面這種方式來顯示使用它

  1. Thread.currentThread().getContextClassLoader().loadClass(name); 

這意味著如果你使用 forName(string name) 方法加載目標(biāo)類,它不會自動使用 contextClassLoader。那些因為代碼上的依賴關(guān)系而懶惰加載的類也不會自動使用 contextClassLoader來加載。

其次線程的 contextClassLoader 默認是從父線程那里繼承過來的,所謂父線程就是創(chuàng)建了當(dāng)前線程的線程。程序啟動時的 main 線程的 contextClassLoader 就是 AppClassLoader。這意味著如果沒有人工去設(shè)置,那么所有的線程的 contextClassLoader 都是 AppClassLoader。

那這個 contextClassLoader 究竟是做什么用的?我們要使用前面提到了類加載器分工與合作的原理來解釋它的用途。

它可以做到跨線程共享類,只要它們共享同一個 contextClassLoader。父子線程之間會自動傳遞 contextClassLoader,所以共享起來將是自動化的。

如果不同的線程使用不同的 contextClassLoader,那么不同的線程使用的類就可以隔離開來。

如果我們對業(yè)務(wù)進行劃分,不同的業(yè)務(wù)使用不同的線程池,線程池內(nèi)部共享同一個 contextClassLoader,線程池之間使用不同的 contextClassLoader,就可以很好的起到隔離保護的作用,避免類版本沖突。

如果我們不去定制 contextClassLoader,那么所有的線程將會默認使用 AppClassLoader,所有的類都將會是共享的。

線程的 contextClassLoader 使用場合比較罕見,如果上面的邏輯晦澀難懂也不必過于計較。

JDK9 增加了模塊功能之后對類加載器的結(jié)構(gòu)設(shè)計做了一定程度的修改,不過類加載器的原理還是類似的,作為類的容器,它起到類隔離的作用,同時還需要依靠雙親委派機制來建立不同的類加載器之間的合作關(guān)系。

責(zé)任編輯:武曉燕 來源: 今日頭條
相關(guān)推薦

2010-03-16 17:30:14

Java多線程編程

2019-07-18 08:10:01

Java開發(fā)代碼

2010-07-05 09:07:42

2015-11-25 09:41:05

數(shù)據(jù)中心

2016-02-22 11:34:17

2011-11-18 09:26:18

Javafinally

2023-11-24 11:24:16

Linux系統(tǒng)

2017-02-24 14:53:16

iOSRunLoop

2024-01-31 07:47:06

C++預(yù)定義宏編程

2009-12-17 10:28:55

熱點路由技術(shù)

2010-08-17 11:31:47

路由技術(shù)

2011-07-10 14:28:49

JAVAIO

2013-05-09 14:48:26

Windows Blu

2025-05-07 00:30:00

SafariURL參數(shù)

2010-08-05 09:56:07

路由技術(shù)

2009-07-09 16:23:36

java jvm

2015-03-27 10:32:03

阿里巴巴IDST

2022-08-05 13:51:32

Python函數(shù)lambda

2010-05-11 10:19:17

VMforceJava云計算

2010-05-17 09:13:35

點贊
收藏

51CTO技術(shù)棧公眾號

主站蜘蛛池模板: 色偷偷噜噜噜亚洲男人 | 日韩有码一区二区三区 | 精品久久久网站 | www.亚洲.com | 国产成人免费 | 国产精品网址 | 黄频免费 | 最新日韩在线 | 九九热国产视频 | 国产日韩欧美精品一区二区 | 成人久草 | 91精品久久久久久久久久入口 | 欧美 中文字幕 | 草草视频在线免费观看 | 有码一区| 精品久久影院 | 一区二区三区回区在观看免费视频 | 天天色综 | 一区二区三区精品视频 | 一区二区三区四区av | 亚洲在线一区二区 | 伊人二区| 日韩国产三区 | 91秦先生艺校小琴 | 日韩在线视频免费观看 | 中文字幕91 | 亚洲视频www | 亚洲高清在线观看 | 免费精品在线视频 | 国产一伦一伦一伦 | 国产成人一区 | 国产精品视频久久久 | 欧美久久大片 | 一区二区三区欧美 | 99爱在线视频| 亚洲一区二区三区在线视频 | 亚洲国产一区二区三区在线观看 | 黄色一级电影在线观看 | 欧美中文 | 黄网站在线播放 | 在线观看亚洲欧美 |