日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

JVM-白话聊一聊JVM类加载和双亲委派机制源码解析

發布時間:2025/3/21 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JVM-白话聊一聊JVM类加载和双亲委派机制源码解析 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

  • Java 執行代碼的大致流程
  • 類加載loadClass的步驟
  • 類加載器和雙親委派機制
  • sun.misc.Launcher源碼解析
    • Launcher實例化
    • Launcher 構造函數
  • 雙親委派機制 源碼解析
    • 雙親委派過程
    • 源碼解析 ClassLoader#loadClass
    • 雙親委派機制的優點
  • 全盤負責委托機制


Java 執行代碼的大致流程

我們先回顧下Java 執行代碼的大致流程

假設要執行A類的main方法

  • 啟動虛擬機 (C++負責創建) 【windows : bin/java.exe調用 jvm.dll Linux : java 調用 libjvm.so 】
  • 創建一個引導類加載器實例 (C++實現)
  • C++ 調用Java代碼,創建JVM啟動器,實例sun.misc.Launcher 【這貨由引導加載器負責加載創建其他類加載器】
  • sun.misc.Launcher.getLauncher() 獲取運行類自己的加載器ClassLoader --> 是AppClassLoader , 通過上圖源碼可知

  • 獲取到ClassLoader后調用loadClass(“A”)方法加載運行的類A

  • 加載完成執行A類的main方法

  • 程序運行結束

  • JVM銷毀


  • 類加載loadClass的步驟

    其中最核心的方法 loadClass ,其實現我們常說的雙親委派機制 ,我們后面展開。

    我們先白話一下類加載的幾個步驟

    加載 ----> 驗證 ----> 準備 ----> 解析 ----> 初始化 ----> 使用 ----> 卸載

    談及比較多的是前五個 ,我們來捋一捋哈 ,不要嘗試死記硬背,嘗試去理解它的邏輯

  • 加載: 我們說jvm執行的java字節碼,編譯后在磁盤上,總得讀取這個字節碼文件吧 ,通過啥讀 IO唄 , 所以第一步肯定是加載字節碼文件
  • 驗證 : JVM總不能說讀到啥就直接運行了吧,你外面有個A.class 里面是一堆JVM規范不認識的內容,也執行不了啊 。 符合JVM規范才能執行后續的步驟,所以第二步是 校驗字節碼文件的正確性
  • 準備 : 給類的靜態變量分配內存,并賦予默認值。 我們的類里,可能會包含一些靜態變量吧 。 比如 public static final int a = 12; 得給a分配個默認值 0 ,再比如 public static User user = new User(); 給 static的變量 User分配內存,并賦默認值null (final修飾的常量,直接賦值)
  • 解析 : 這個地方不是很好理解, 解析是什么意思呢?將符號引用替換為直接引用。 符號引用 ? 直接引用? what ? ------------- 我們的類的靜態方法 比如main方法 其實在Java中有個叫法 都是叫符號 。 這個階段就會吧 一些靜態方法(符號引用,比如剛才說的main方法)替換為指向數據所存內存的指針或者句柄等(直接引用)【找到具體在內存中的位置】。 這個就是靜態鏈接過程(在類加載期間完成)。 動態鏈接是在程序運行期間完成的將符號引用替換為直接引用 (比如某個普通方法的調用)
  • 初始化: 上面的步驟完事兒以后,這一步主要是對類的靜態變量初始化為指定的值,執行靜態代碼塊。 比如剛才第二步的 public static final int a = 12; ,第二步給static變量賦了默認值,這一步就該把12賦值給它了。 還有 static的 User public static User user = new User(); 實例化User

  • 類加載器和雙親委派機制

    剛才說了類加載器中loadClass方法實現了雙親委派的機制,那我們需要先了解下有哪幾種類加載器

    主要有4種

  • 引導類加載器:負責加載支撐JVM運行的位于JRE的lib目錄下的核心類庫,比如rt.jar、charsets.jar等
  • 擴展類加載器:負責加載支撐JVM運行的位于JRE的lib目錄下的ext擴展目錄中的JAR類包
  • 應用程序類加載器:負責加載ClassPath路徑下的類包,主要就是加載我們應用中自己寫的那些類
  • 自定義加載器:負責加載用戶自定義路徑下的類包
  • 我們來看看 幾種不同的類加載器

    public class ClassLoadTest {public static void main(String[] args) {// 核心rt.jar中的類加載器 是C++加載的,因此這里為null System.out.println(String.class.getClassLoader());// 擴展包的加載器 ExtClassLoaderSystem.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader());// 應用加載器 AppClassLoaderSystem.out.println(ClassLoadTest.class.getClassLoader());System.out.println("");// 獲取系統ClassLoaderClassLoader appClassLoader = ClassLoader.getSystemClassLoader();// appClassLoader的父加載器ClassLoader extClassLoader = appClassLoader.getParent();// extClassLoader的父加載器ClassLoader boostrapClassLoader = extClassLoader.getParent();System.out.println("the bootstrapLoader : " + boostrapClassLoader);System.out.println("the extClassloader : " + extClassLoader);System.out.println("the appClassLoader : "+ appClassLoader);System.out.println("");System.out.println("==============bootstrapLoader加載的文件====================");URL[] urLs = Launcher.getBootstrapClassPath().getURLs();for (int i = 0; i < urLs.length; i++) {System.out.println(urLs[i]);}System.out.println("");System.out.println("==============extClassloader加載的文件====================");System.out.println(System.getProperty("java.ext.dirs"));System.out.println("");System.out.println("==============appClassLoader 加載的文件====================");System.out.println(System.getProperty("java.class.path"));} }

    輸出

    null sun.misc.Launcher$ExtClassLoader@29453f44 sun.misc.Launcher$AppClassLoader@18b4aac2the bootstrapLoader : null the extClassloader : sun.misc.Launcher$ExtClassLoader@29453f44 the appClassLoader : sun.misc.Launcher$AppClassLoader@18b4aac2==============bootstrapLoader加載的文件==================== file:/E:/Program%20Files/Java/jdk1.8.0_161/jre/lib/resources.jar file:/E:/Program%20Files/Java/jdk1.8.0_161/jre/lib/rt.jar file:/E:/Program%20Files/Java/jdk1.8.0_161/jre/lib/sunrsasign.jar file:/E:/Program%20Files/Java/jdk1.8.0_161/jre/lib/jsse.jar file:/E:/Program%20Files/Java/jdk1.8.0_161/jre/lib/jce.jar file:/E:/Program%20Files/Java/jdk1.8.0_161/jre/lib/charsets.jar file:/E:/Program%20Files/Java/jdk1.8.0_161/jre/lib/jfr.jar file:/E:/Program%20Files/Java/jdk1.8.0_161/jre/classes==============extClassloader加載的文件==================== E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext==============appClassLoader 加載的文件==================== E:\Program Files\Java\jdk1.8.0_161\jre\lib\charsets.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\deploy.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\access-bridge-64.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\cldrdata.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\dnsns.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\jaccess.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\jfxrt.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\localedata.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\nashorn.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\sunec.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\sunjce_provider.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\sunmscapi.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\sunpkcs11.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\ext\zipfs.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\javaws.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\jce.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\jfr.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\jfxswt.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\jsse.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\management-agent.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\plugin.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\resources.jar;E:\Program Files\Java\jdk1.8.0_161\jre\lib\rt.jar;D:\IdeaProjects\GOF23\target\classes;C:\Program Files\JetBrains\IntelliJ IDEA 2019.3.1\lib\idea_rt.jar

    看看appClassLoader 咋加載這么多? 其實它并沒有加載這么多,除了 D:\IdeaProjects\GOF23\target\classes; 是它加載的,剩下的都是他的父加載器給他干的。


    sun.misc.Launcher源碼解析

    JVM啟動時,C++會實例化JVM啟動器實例sun.misc.Launcher ,所以很有必要研究一下Launcher的源碼 。

    Launcher實例化

    private static Launcher launcher = new Launcher();

    采用了 餓漢模式 靜態域的方式 實現了單例模式 ,保證一個JVM虛擬機內只有一個sun.misc.Launcher實例。

    Launcher 構造函數

    實例化,調用構造函數,我們看下它的構造函數干了啥?

    public Launcher() {Launcher.ExtClassLoader var1;try {//構造擴展類加載器,在構造的過程中將其父加載器設置為nullvar1 = Launcher.ExtClassLoader.getExtClassLoader();} catch (IOException var10) {throw new InternalError("Could not create extension class loader", var10);}try { //構造應用類加載器,在構造的過程中將其父加載器設置為ExtClassLoader,//Launcher的loader屬性值是AppClassLoader,我們一般都是用這個類加載器來加載我們自己寫的應用程序this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);} catch (IOException var9) {throw new InternalError("Could not create application class loader", var9);}Thread.currentThread().setContextClassLoader(this.loader);...............}}

    Launcher構造方法內部, 創建了兩個類加載器,分別是sun.misc.Launcher.ExtClassLoader(擴展類加載器)和sun.misc.Launcher.AppClassLoader(應用類加載器)。

    JVM默認使用Launcher的getClassLoader()方法返回的類加載器AppClassLoader的實例加載我們的應用程序。


    雙親委派機制 源碼解析

    雙親委派過程

    通俗的說: 當我們需要加載某個類時會先委托父加載器尋找目標類,找不到再委托上層父加載器加載,如果所有父加載器在自己的加載類路徑下都找不到目標類,則在自己的類加載路徑中查找并載入目標類。

    舉個例子,我們有個類A.class ,最先會找應用程序類加載器AppClassLoader 加載,AppClassLoader 會先委托擴展類加載器ExtClassLoader加載,擴展類加載器再委托引導類加載器BootClassLoader,頂層引導類加載器BootClassLoader在自己的類加載路徑里 沒找到A類,則向下退回加載A類的請求,擴展類加載器ExtClassLoader收到回復就自己加載,在自己的類加載路徑里找了半天也沒找到A類,又向下退回A類的加載請求給應用程序類加載器AppClassLoader ,應用程序類加載器 在自己的類加載路徑里找A類,結果找到了就自己加載了。。


    源碼解析 ClassLoader#loadClass

    loadClass實現了雙親委派的功能,我們有必要好好的研究一下

    既然都是委托向上查找,那我們來看下應用程序類加載器AppClassLoader加載類的雙親委派機制源碼,AppClassLoader的loadClass方法最終會調用其父類ClassLoader的loadClass方法

    protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{synchronized (getClassLoadingLock(name)) {// First, check if the class has already been loaded// 檢查當前類加載器是否已經加載了該類 ,加載直接返回Class<?> c = findLoadedClass(name);if (c == null) {long t0 = System.nanoTime();try {//如果當前加載器父加載器不為空則委托父加載器加載該類if (parent != null) {c = parent.loadClass(name, false);} else { //如果當前加載器父加載器為空則委托引導類加載器加載該類c = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {}if (c == null) {// If still not found, then invoke findClass in order// to find the class.long t1 = System.nanoTime();//調用URLClassLoader的findClass方法在加載器的類路徑里查找并加載該類c = findClass(name);// this is the defining class loader; record the statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;}}

    看注釋~

    總結一下幾個步驟

  • 首先,檢查一下指定名稱的類是否已經加載過,如果加載過了,就不需要再加載,直接返回。
  • 如果此類沒有加載過,那么,再判斷一下是否有父加載器;如果有父加載器,則由父加載器加載(即調用parent.loadClass(name, false);).或者是調用bootstrap類加載器來加載。
  • 如果父加載器及bootstrap類加載器都沒有找到指定的類,那么調用當前類加載器的findClass方法 【調用URLClassLoader的findClass方法在加載器的類路徑里查找并加載該類】來完成類加載。

  • 雙親委派機制的優點

  • 沙箱安全機制:比如我們自己寫的java.lang.String.class類不會被加載,這樣便可以防止核心API庫被隨意篡改
  • 避免類的重復加載:當父親已經加載了該類時,就沒有必要子ClassLoader再加載一次,保證被加載類的唯一性

  • 全盤負責委托機制

    這個比較好理解

    “全盤負責”是指當一個ClassLoder裝載一個類時,除非顯示的使用另外一個ClassLoder,該類所依賴及引用的類也由這個ClassLoder載入。

    比如我們的類 A中引用了 類B,由于全盤負責委托機制 ,類B也將有加載類A的加載器來加載,除非你顯示的使用另外一個ClassLoder。


    總結

    以上是生活随笔為你收集整理的JVM-白话聊一聊JVM类加载和双亲委派机制源码解析的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。