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

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

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

類加載過程

類加載的時機

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

類被主動加載的 7 種情況

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

REF_invokeStatic句柄對應的類沒有被初始化則初始化。

其它加載情況

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

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

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

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

測試例子 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("子類構造方法"); 
  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("父類構造方法"); 
  31.     } 
  32.  
  33.     public static void find() { 
  34.         System.out.println("靜態(tài)方法"); 
  35.     } 
  36.  
  37. //代碼塊和構造方法執(zhí)行順序 
  38. //1).父類靜態(tài)代碼塊 
  39. //2).子類靜態(tài)代碼塊 
  40. //3).父類代碼塊 
  41. //4).父類構造方法 
  42. //5).子類代碼塊 
  43. //6).子類構造方法 

測試例子 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 關鍵字調(diào)用對象等,在加載階段會在內(nèi)存中生成這個類的 java.lang.Class 對象, 作為方法區(qū)這個類的各種數(shù)據(jù)的訪問入口。

驗證

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

準備

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

解析

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

初始化

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

類加載器

  • **_引導類加載器(Bootstrap Class Loader) _**負責加載 \lib\ 目錄或者被 -Dbootclaspath 參數(shù)指定的類, 比如: rt.jar, tool.jar 等 。
  • 拓展類加載器(Extension Class Loader) 負責加載 \lib\ext\ 或 -Djava.ext.dirs 選項所指定目錄下的類和 jar包。
  • 應用程序類加載器(System Class Loader) 負責加載 CLASSPATH 或 -Djava.class.path所指定的目錄下的類和 jar 包。
  • 自定義類加載器:負責加載用戶自定義包路徑下的類包,通過 ClassLoader 的子類實現(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.     } 

雙親委派機制

什么是雙親委派機制?

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

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

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

  1. // loadClass 
  2. protected Class<?> loadClass(String name, boolean resolve) 
  3.   throws ClassNotFoundException 
  4.   synchronized (getClassLoadingLock(name)) { 
  5.     // 首先檢查當前類是否被加載 
  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.           // 引導類加載器嘗試加載 
  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. // 類加載器的包含關系 
  43. public abstract class ClassLoader { 
  44.  
  45.     private static native void registerNatives(); 
  46.     static { 
  47.         registerNatives(); 
  48.     } 
  49.    
  50.    // 當前 ClassLoader 和 parent ClassLoader 的包含關系 
  51.     private final ClassLoader parent; 
  52.    

總結(jié):

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

設計雙親委派機制的目的?

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

自定義類加載器

自定義類加載器加載類,下面是一個簡單的 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 只對當前 webapp 可見, 比如加載 war 包里面相關的類,每個 war 包應用都有自己的 webappClassLoader 對象,對應不同的命名空間,實現(xiàn)相互隔離,比如 war 包中可以引入不同的 spring 版本,實現(xiàn)多個 spring 版本 應用的同時運行。

總結(jié):

從圖中的委派關系中可以看出:

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

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

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

參考資料

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

Apache Tomcat Documentation

 

責任編輯:姜華 來源: 運維開發(fā)故事
相關推薦

2022-01-17 11:28:55

JVM 虛擬機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-23 08:59:35

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

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ù)庫數(shù)據(jù)存儲

2023-03-29 07:45:58

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

2021-04-14 14:16:58

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

2021-04-08 11:00:56

CountDownLaJava進階開發(fā)

2021-07-21 09:48:20

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

2022-04-29 14:38:49

class文件結(jié)構分析

2024-06-13 08:34:48

2021-03-12 09:21:31

MySQL數(shù)據(jù)庫邏輯架構
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 久久国产视频播放 | 99久久精品视频免费 | 色婷婷一区二区三区四区 | 久一久 | 亚洲欧美日韩中文在线 | 日韩中文字幕一区 | 别c我啊嗯国产av一毛片 | 99爱在线观看 | 一区二区三区精品在线 | 欧美精品一区二区在线观看 | 人人玩人人添人人澡欧美 | 国产精品国产精品国产专区不卡 | 二区视频 | 免费的一级视频 | 69精品久久久久久 | 亚洲精品视频在线看 | 99精品欧美一区二区蜜桃免费 | 欧美一区二区三区的 | 日韩欧美在线观看视频 | 国产网站在线免费观看 | 天天操天天干天天曰 | 黄色男女网站 | 久久另类视频 | 日本天堂视频在线观看 | 国产天堂 | 日本中文字幕在线视频 | 久久91 | 国产精品一区二区三区在线播放 | 成人在线亚洲 | 91一区二区 | 四虎影院新地址 | 亚洲精品久久 | www.日本精品 | 国产精品毛片一区二区三区 | 久久久人成影片一区二区三区 | 在线看无码的免费网站 | 激情五月婷婷综合 | 国产精品精品久久久 | 天天操夜夜拍 | 久久久久久成人 | 欧美另类视频 |