偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

一篇帶給你JVM 類(lèi)加載過(guò)程解析

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

類(lèi)加載過(guò)程

類(lèi)加載的時(shí)機(jī)

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

類(lèi)被主動(dòng)加載的 7 種情況

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

REF_invokeStatic句柄對(duì)應(yīng)的類(lèi)沒(méi)有被初始化則初始化。

其它加載情況

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

  • 在初始化一個(gè)類(lèi)時(shí),并不會(huì)先初始化它所實(shí)現(xiàn)的接口
  • 在初始化一個(gè)接口時(shí),并不會(huì)先初始化它的父類(lèi)接口
  • 因此,一個(gè)父接口并不會(huì)因?yàn)樗淖咏涌诨蛘邔?shí)現(xiàn)了類(lèi)的初始化而初始化,只有當(dāng)程序首次被使用特定接口的靜態(tài)變量時(shí),才會(huì)導(dǎo)致該接口的初始化。

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

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

測(cè)試?yán)?1:

  1. public class Test_2 extends Test_2_A { 
  2.     static { 
  3.         System.out.println("子類(lèi)靜態(tài)代碼塊"); 
  4.     } 
  5.  
  6.     { 
  7.         System.out.println("子類(lèi)代碼塊"); 
  8.     } 
  9.  
  10.     public Test_2() { 
  11.         System.out.println("子類(lèi)構(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("父類(lèi)靜態(tài)代碼塊"); 
  23.     } 
  24.  
  25.     { 
  26.         System.out.println("父類(lèi)代碼塊"); 
  27.     } 
  28.  
  29.     public Test_2_A() { 
  30.         System.out.println("父類(lèi)構(gòu)造方法"); 
  31.     } 
  32.  
  33.     public static void find() { 
  34.         System.out.println("靜態(tài)方法"); 
  35.     } 
  36.  
  37. //代碼塊和構(gòu)造方法執(zhí)行順序 
  38. //1).父類(lèi)靜態(tài)代碼塊 
  39. //2).子類(lèi)靜態(tài)代碼塊 
  40. //3).父類(lèi)代碼塊 
  41. //4).父類(lèi)構(gòu)造方法 
  42. //5).子類(lèi)代碼塊 
  43. //6).子類(lèi)構(gòu)造方法 

測(cè)試?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 

類(lèi)加載流程

加載

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

驗(yàn)證

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

準(zhǔn)備

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

解析

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

初始化

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

類(lèi)加載器

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

測(cè)試文件:

  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ī)制?

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

類(lèi)加載和雙親委派模型如下圖所示

我們?cè)賮?lái)看看 ClassLoader 類(lèi)的 loadClass 方法

  1. // loadClass 
  2. protected Class<?> loadClass(String name, boolean resolve) 
  3.   throws ClassNotFoundException 
  4.   synchronized (getClassLoadingLock(name)) { 
  5.     // 首先檢查當(dāng)前類(lèi)是否被加載 
  6.     Class<?> c = findLoadedClass(name); 
  7.     if (c == null) { 
  8.       long t0 = System.nanoTime(); 
  9.       try { 
  10.         if (parent != null) { 
  11.           // 如果父類(lèi)類(lèi)加載器不為空,先嘗試父類(lèi)加載來(lái)加載 
  12.           c = parent.loadClass(namefalse); 
  13.         } else { 
  14.           // 引導(dǎo)類(lèi)加載器嘗試加載 
  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. // 類(lèi)加載器的包含關(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. 不是樹(shù)形結(jié)構(gòu)(只是邏輯樹(shù)形結(jié)構(gòu)),而是包含/包裝關(guān)系。
  2. 加載順序,應(yīng)用類(lèi)加載器,拓展加載器,系統(tǒng)加載器。
  3. 如果有一個(gè)類(lèi)加載器能夠成功加載 Test 類(lèi),那么這個(gè)類(lèi)加載器被稱(chēng)為定義類(lèi)加載器,所有可能返回 Class 對(duì)象引用的類(lèi)加載器(包括定義類(lèi)加載器)都被稱(chēng)為初始類(lèi)加載器。

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

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

自定義類(lèi)加載器

自定義類(lèi)加載器加載類(lèi),下面是一個(gè)簡(jiǎ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 全路徑類(lèi)名 
  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 類(lèi)加載器

Tomcat 中的類(lèi)加載器模型

Tomcat 類(lèi)加載器說(shuō)明

tomcat 的幾個(gè)主要類(lèi)加載器:

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

總結(jié):

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

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

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

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

參考資料

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

Apache Tomcat Documentation

 

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

2022-01-17 11:28:55

JVM 虛擬機(jī)Java

2023-04-09 21:39:48

JavaScript開(kāi)源

2023-02-27 10:17:05

EventBus觀察者模式

2021-07-12 06:11:14

SkyWalking 儀表板UI篇

2022-02-25 15:50:05

OpenHarmonToggle組件鴻蒙

2021-04-23 08:59:35

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

2021-07-08 07:30:13

Webpack 前端Tree shakin

2021-04-14 07:55:45

Swift 協(xié)議Protocol

2023-03-13 09:31:04

2021-05-08 08:36:40

ObjectString前端

2021-10-28 08:51:53

GPIO軟件框架 Linux

2021-06-21 14:36:46

Vite 前端工程化工具

2021-01-28 08:55:48

Elasticsear數(shù)據(jù)庫(kù)數(shù)據(jù)存儲(chǔ)

2023-03-29 07:45:58

VS編輯區(qū)編程工具

2021-04-14 14:16:58

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

2021-04-08 11:00:56

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

2021-07-21 09:48:20

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

2022-04-29 14:38:49

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

2024-06-13 08:34:48

2021-03-12 09:21:31

MySQL數(shù)據(jù)庫(kù)邏輯架構(gòu)
點(diǎn)贊
收藏

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