自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

一篇帶給你JVM 類加載過程解析

開發(fā) 后端
一個類型被加載到虛擬機(jī)內(nèi)存中開始,到卸載出內(nèi)存為止、它的整個生命周期將會經(jīng)歷加載、驗證、準(zhǔn)備、解析、初始化、使用、卸載七個階段。

類加載過程

類加載的時機(jī)

一個類型被加載到虛擬機(jī)內(nèi)存中開始,到卸載出內(nèi)存為止、它的整個生命周期將會經(jīng)歷加載、驗證、準(zhǔn)備、解析、初始化、使用、卸載七個階段。其中驗證、準(zhǔn)備、解析為連接

類被主動加載的 7 種情況

  1. 創(chuàng)建類的實(shí)例, 比如:new Object();
  2. 訪問某個類或接口的靜態(tài)變量,或者對該靜態(tài)變量賦值;
  3. 調(diào)用類的靜態(tài)方法;
  4. 反射(如 Class.forName("com.test.Test");
  5. 初始化一個類的子類;
  6. Java虛擬機(jī)啟動時被標(biāo)記為啟動類的類, 就是包含 main 方法的類(Java Test);
  7. JDK1.7開始提供的動態(tài)語言支持,java.lang.invoke.MethodHandle實(shí)例的解析結(jié)果REF_getStatic, REF_putStatic,;

REF_invokeStatic句柄對應(yīng)的類沒有被初始化則初始化。

其它加載情況

當(dāng) Java 虛擬機(jī)初始化一個類時,要求它所有的父類都被初始化,單這一條規(guī)則并不適用于接口。

  • 在初始化一個類時,并不會先初始化它所實(shí)現(xiàn)的接口
  • 在初始化一個接口時,并不會先初始化它的父類接口
  • 因此,一個父接口并不會因為他的子接口或者實(shí)現(xiàn)了類的初始化而初始化,只有當(dāng)程序首次被使用特定接口的靜態(tài)變量時,才會導(dǎo)致該接口的初始化。

只有當(dāng)前程序訪問的靜態(tài)變量或靜態(tài)方法確實(shí)在當(dāng)前類或當(dāng)前接口定義時,才可認(rèn)為是對接口或類的主動使用。

調(diào)用 ClassLoader 類的 loadClass 方法加載一類,并不是對類的主動使用,不會導(dǎo)致類的初始化。

測試?yán)?1:

  1. public class Test_2 extends Test_2_A { 
  2.     static { 
  3.         System.out.println("子類靜態(tài)代碼塊"); 
  4.     } 
  5.  
  6.     { 
  7.         System.out.println("子類代碼塊"); 
  8.     } 
  9.  
  10.     public Test_2() { 
  11.         System.out.println("子類構(gòu)造方法"); 
  12.     } 
  13.  
  14.     public static void main(String[] args) { 
  15.         new Test_2(); 
  16.     } 
  17.  
  18.  
  19. class Test_2_A { 
  20.  
  21.     static { 
  22.         System.out.println("父類靜態(tài)代碼塊"); 
  23.     } 
  24.  
  25.     { 
  26.         System.out.println("父類代碼塊"); 
  27.     } 
  28.  
  29.     public Test_2_A() { 
  30.         System.out.println("父類構(gòu)造方法"); 
  31.     } 
  32.  
  33.     public static void find() { 
  34.         System.out.println("靜態(tài)方法"); 
  35.     } 
  36.  
  37. //代碼塊和構(gòu)造方法執(zhí)行順序 
  38. //1).父類靜態(tài)代碼塊 
  39. //2).子類靜態(tài)代碼塊 
  40. //3).父類代碼塊 
  41. //4).父類構(gòu)造方法 
  42. //5).子類代碼塊 
  43. //6).子類構(gòu)造方法 

測試?yán)?2:

  1. public class Test_1 { 
  2.     public static void main(String[] args) { 
  3.         System.out.println(Test_1_B.str); 
  4.     } 
  5.  
  6. class Test_1_A { 
  7.     public static String str = "A str"
  8.  
  9.     static { 
  10.         System.out.println("A Static Block"); 
  11.     } 
  12.  
  13. class Test_1_B extends Test_1_A { 
  14.     static { 
  15.         System.out.println("B Static Block"); 
  16.     } 
  17.  
  18. //輸出結(jié)果 
  19. //A Static Block 
  20. //A str 

類加載流程

加載

在硬盤上查找并且通過 IO 讀入字節(jié)碼文件,使用到該類的時候才會被加載,例如調(diào)用 main 方法, new 關(guān)鍵字調(diào)用對象等,在加載階段會在內(nèi)存中生成這個類的 java.lang.Class 對象, 作為方法區(qū)這個類的各種數(shù)據(jù)的訪問入口。

驗證

校驗字節(jié)碼文件的正確性

準(zhǔn)備

給類的靜態(tài)變量分配內(nèi)存,并且賦予默認(rèn)值

解析

將符號引用替換為直接引用,該節(jié)點(diǎn)會把一些靜態(tài)方法(符號引用,比如 main() 方法)替換為指向數(shù)據(jù)所存內(nèi)存的指針或句柄等(直接引用),這就是所謂的靜態(tài)鏈接過程(類加載期間完成),動態(tài)鏈接是在程序運(yùn)行期間完成的將符號引用替換為直接引用。

初始化

對類的靜態(tài)變量初始化為指定的值,執(zhí)行靜態(tài)代碼塊。

類加載器

  • **_引導(dǎo)類加載器(Bootstrap Class Loader) _**負(fù)責(zé)加載 \lib\ 目錄或者被 -Dbootclaspath 參數(shù)指定的類, 比如: rt.jar, tool.jar 等 。
  • 拓展類加載器(Extension Class Loader) 負(fù)責(zé)加載 \lib\ext\ 或 -Djava.ext.dirs 選項所指定目錄下的類和 jar包。
  • 應(yīng)用程序類加載器(System Class Loader) 負(fù)責(zé)加載 CLASSPATH 或 -Djava.class.path所指定的目錄下的類和 jar 包。
  • 自定義類加載器:負(fù)責(zé)加載用戶自定義包路徑下的類包,通過 ClassLoader 的子類實(shí)現(xiàn) Class 的加載。

測試文件:

  1. public class TestJVMClassLoader { 
  2.  
  3.     public static void main(String[] args) { 
  4.         System.out.println(String.class.getClassLoader()); 
  5.         System.out.println(DESKeyFactory.class.getClassLoader()); 
  6.         System.out.println(TestJVMClassLoader.class.getClassLoader()); 
  7.  
  8.         System.out.println(); 
  9.         ClassLoader appClassLoader = ClassLoader.getSystemClassLoader(); 
  10.         ClassLoader extClassLoader = appClassLoader.getParent(); 
  11.         ClassLoader bootstrapClassLoader = extClassLoader.getParent(); 
  12.  
  13.         System.out.println("bootstrapClassLoader: " + bootstrapClassLoader); 
  14.         System.out.println("extClassLoader: " + extClassLoader); 
  15.         System.out.println("appClassLoader: " + appClassLoader); 
  16.  
  17.         System.out.println(); 
  18.         System.out.println("bootstrapLoader 加載以下文件:"); 
  19.         URL[] urls = Launcher.getBootstrapClassPath().getURLs(); 
  20.         for (URL url : urls) { 
  21.             System.out.println(url); 
  22.         } 
  23.         System.out.println(); 
  24.         System.out.println("extClassLoader 加載以下文件:"); 
  25.         System.out.println(System.getProperty("java.ext.dirs")); 
  26.         System.out.println(); 
  27.         System.out.println("appClassLoader 加載以下文件:"); 
  28.         System.out.println(System.getProperty("java.class.path")); 
  29.     } 

雙親委派機(jī)制

什么是雙親委派機(jī)制?

一個類加載器收到了類加載的請求, 它首先不會自己去嘗試自己去加載這個類,而是把這個請求委派給父類加載器去完成,每一個層次的類加載器都是如此,因此所有的請求最終都應(yīng)該傳送到最頂層的啟動類加載器中,只有當(dāng)父加載器反饋?zhàn)约簾o法完成這個加載請求(即搜索范圍中沒有找到所需的類)時,子加載器才會嘗試自己完成加載。

類加載和雙親委派模型如下圖所示

我們再來看看 ClassLoader 類的 loadClass 方法

  1. // loadClass 
  2. protected Class<?> loadClass(String name, boolean resolve) 
  3.   throws ClassNotFoundException 
  4.   synchronized (getClassLoadingLock(name)) { 
  5.     // 首先檢查當(dāng)前類是否被加載 
  6.     Class<?> c = findLoadedClass(name); 
  7.     if (c == null) { 
  8.       long t0 = System.nanoTime(); 
  9.       try { 
  10.         if (parent != null) { 
  11.           // 如果父類類加載器不為空,先嘗試父類加載來加載 
  12.           c = parent.loadClass(namefalse); 
  13.         } else { 
  14.           // 引導(dǎo)類加載器嘗試加載 
  15.           c = findBootstrapClassOrNull(name); 
  16.         } 
  17.       } catch (ClassNotFoundException e) { 
  18.         // ClassNotFoundException thrown if class not found 
  19.         // from the non-null parent class loader 
  20.       } 
  21.  
  22.       if (c == null) { 
  23.         // If still not found, then invoke findClass in order 
  24.         // to find the class. 
  25.         long t1 = System.nanoTime(); 
  26.         // 嘗試自己加載  
  27.         c = findClass(name); 
  28.  
  29.         // this is the defining class loader; record the stats 
  30.         sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); 
  31.         sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); 
  32.         sun.misc.PerfCounter.getFindClasses().increment(); 
  33.       } 
  34.     } 
  35.     if (resolve) { 
  36.       resolveClass(c); 
  37.     } 
  38.     return c; 
  39.   } 
  40.  
  41.  
  42. // 類加載器的包含關(guān)系 
  43. public abstract class ClassLoader { 
  44.  
  45.     private static native void registerNatives(); 
  46.     static { 
  47.         registerNatives(); 
  48.     } 
  49.    
  50.    // 當(dāng)前 ClassLoader 和 parent ClassLoader 的包含關(guān)系 
  51.     private final ClassLoader parent; 
  52.    

總結(jié):

  1. 不是樹形結(jié)構(gòu)(只是邏輯樹形結(jié)構(gòu)),而是包含/包裝關(guān)系。
  2. 加載順序,應(yīng)用類加載器,拓展加載器,系統(tǒng)加載器。
  3. 如果有一個類加載器能夠成功加載 Test 類,那么這個類加載器被稱為定義類加載器,所有可能返回 Class 對象引用的類加載器(包括定義類加載器)都被稱為初始類加載器。

設(shè)計雙親委派機(jī)制的目的?

  1. 保證 Java 核心庫的類型安全:所有的java 應(yīng)用都會至少引用 java.lang.Object 類, 也就是說在運(yùn)行期, java.lang.Object 的這個類會被加載到 Java 虛擬機(jī)中,如果這個加載過程是由 Java 應(yīng)用自己的類加載器所完成的,那么很有可能會在 JVM 中存在多個版本的 java.lang.Object 類,而且這些類之間還是不兼容的?;ゲ豢梢姷?正是命名空間發(fā)揮著作用)借助于雙親委托機(jī)制,Java 核心庫中的類加載工作都是由啟動類加載器統(tǒng)一來完成的。從而確保了Java 應(yīng)用所使用的都是同一個版本的 Java 核心類庫,他們之間是相互兼容的。
  2. 可以確保 Java 核心庫所提供的類不會被自定義的類所替代。
  3. 不同的類加載器可以為相同類(binary name)的類創(chuàng)建額外的命名空間。相同名稱的類可以并存在Java虛擬機(jī)中,只需要不同的類加載器來加載他們即可,不同的類加載器的類之間是不兼容的,這相當(dāng)于在JAVA虛擬機(jī)內(nèi)部創(chuàng)建了一個又一個相互隔離的Java類空間,這類技術(shù)在很多框架中得到了實(shí)際運(yùn)用。

自定義類加載器

自定義類加載器加載類,下面是一個簡單的 Demo

  1. import java.io.ByteArrayOutputStream; 
  2. import java.io.File; 
  3. import java.io.FileInputStream; 
  4. import java.io.InputStream; 
  5.  
  6. public class ClassLoaderTest extends ClassLoader { 
  7.  
  8.     private static String rxRootPath; 
  9.  
  10.     static { 
  11.         rxRootPath = "/temp/class/"
  12.     } 
  13.  
  14.     @Override 
  15.     public Class findClass(String name) { 
  16.         byte[] b = loadClassData(name); 
  17.         return defineClass(name, b, 0, b.length); 
  18.     } 
  19.  
  20.     /** 
  21.      * 讀取 .class 文件為字節(jié)數(shù)組 
  22.      * 
  23.      * @param name 全路徑類名 
  24.      * @return 
  25.      */ 
  26.     private byte[] loadClassData(String name) { 
  27.         try { 
  28.             String filePath = fullClassName2FilePath(name); 
  29.             InputStream is = new FileInputStream(new File(filePath)); 
  30.             ByteArrayOutputStream bos = new ByteArrayOutputStream(); 
  31.             byte[] buf = new byte[2048]; 
  32.             int r; 
  33.             while ((r = is.read(buf)) != -1) { 
  34.                 bos.write(buf, 0, r); 
  35.             } 
  36.             return bos.toByteArray(); 
  37.         } catch (Throwable e) { 
  38.             e.printStackTrace(); 
  39.         } 
  40.         return null
  41.     } 
  42.  
  43.     /** 
  44.      * 全限定名轉(zhuǎn)換為文件路徑 
  45.      * 
  46.      * @param name 
  47.      * @return 
  48.      */ 
  49.     private String fullClassName2FilePath(String name) { 
  50.         return rxRootPath + name.replace(".""//") + ".class"
  51.     } 
  52.  
  53.     public static void main(String[] args) throws ClassNotFoundException { 
  54.         ClassLoaderTest classLoader = new ClassLoaderTest(); 
  55.         String className = "com.test.TestAA"
  56.  
  57.         Class clazz = classLoader.loadClass(className); 
  58.         System.out.println(clazz.getClassLoader()); 
  59.         // 輸出結(jié)果  
  60.         //cn.xxx.xxx.loader.ClassLoaderTest@3764951d 
  61.     } 

Tomcat 類加載器

Tomcat 中的類加載器模型

Tomcat 類加載器說明

tomcat 的幾個主要類加載器:

  • commonLoader:Tomcat 最基本的類加載器, 加載路徑中的 class 可以被 Tomcat 容器本身以及各個 WebApp 訪問。
  • catalinaLoader:Tomcat 容器私有的類加載器 加載路徑中的 class 對于 Webapp 不可見;
  • sharaLoader: 各個Webapp 共享的類加載器, 加載路徑中的 class 對于所有 webapp 可見, 但是對于 Tomcat 容器不可見。
  • webappLoader: 各個 Webapp 私有的類加載, 加載路徑中的 class 只對當(dāng)前 webapp 可見, 比如加載 war 包里面相關(guān)的類,每個 war 包應(yīng)用都有自己的 webappClassLoader 對象,對應(yīng)不同的命名空間,實(shí)現(xiàn)相互隔離,比如 war 包中可以引入不同的 spring 版本,實(shí)現(xiàn)多個 spring 版本 應(yīng)用的同時運(yùn)行。

總結(jié):

從圖中的委派關(guān)系中可以看出:

Commonclassloader 能加載的類都可以被 Catalinaclassloader和 Sharedclassloadert 使用, 從而實(shí)現(xiàn)了公有類庫的共用,而Catalinaclassloader 和 Sharedclassloader自己能加載的類則與對方相互隔離 Webappclassloader 可以使用 Shared Loader 加載到的類,但各個 Webappclassloader 實(shí)例之間相互隔離而 Jasper Loader 的加載范圍僅僅是這個 JSP 文件所編譯出來的那一個 . class 文件,它出現(xiàn)的目的就是為了被丟棄: 當(dāng) Web 容器檢測到 JSP 文件被修改時,會替換掉目前的 Jasperloader 的實(shí)例,并通過再建立一個新的 Jsp 類加載器來實(shí)現(xiàn) JSP 文件的熱加載功能。

Tomcat這種類加載機(jī)制違背了java推薦的雙親委派模型了嗎? 答案是: 違背了

tomcat不是這樣實(shí)現(xiàn), tomcat為了實(shí)現(xiàn)隔離性, 沒有遵守這個約定, 每個 webapp Loader加載自己的目錄下的 class'文件,不會傳遞給父類加載器,打破了雙親委派機(jī)制

參考資料

《深入理解 Java 虛擬機(jī)》 第三版 周志明

Apache Tomcat Documentation

 

責(zé)任編輯:姜華 來源: 運(yùn)維開發(fā)故事
相關(guān)推薦

2022-01-17 11:28:55

JVM 虛擬機(jī)Java

2023-04-09 21:39:48

JavaScript開源

2023-02-27 10:17:05

EventBus觀察者模式

2021-07-12 06:11:14

SkyWalking 儀表板UI篇

2022-02-25 15:50:05

OpenHarmonToggle組件鴻蒙

2021-04-14 07:55:45

Swift 協(xié)議Protocol

2021-04-23 08:59:35

ClickHouse集群搭建數(shù)據(jù)庫

2021-05-08 08:36:40

ObjectString前端

2021-07-08 07:30:13

Webpack 前端Tree shakin

2023-03-13 09:31:04

2021-10-28 08:51:53

GPIO軟件框架 Linux

2021-07-21 09:48:20

etcd-wal模塊解析數(shù)據(jù)庫

2021-04-08 11:00:56

CountDownLaJava進(jìn)階開發(fā)

2021-06-21 14:36:46

Vite 前端工程化工具

2021-01-28 08:55:48

Elasticsear數(shù)據(jù)庫數(shù)據(jù)存儲

2021-04-01 10:51:55

MySQL鎖機(jī)制數(shù)據(jù)庫

2021-04-14 14:16:58

HttpHttp協(xié)議網(wǎng)絡(luò)協(xié)議

2022-03-22 09:09:17

HookReact前端

2022-04-29 14:38:49

class文件結(jié)構(gòu)分析

2023-03-29 07:45:58

VS編輯區(qū)編程工具
點(diǎn)贊
收藏

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