java

從JDK原始碼級別徹底剖析JVM類載入機制

·# 類載入機制深度解析

個人的理解為大家分享從JDK原始碼級別徹底剖析JVM類載入機,如有錯誤十分抱歉,還請指出博主會立馬改正。


一、類載入執行全過程

當我們用java命令執行某個類的main函式啟動程式時,首先需要透過類載入器把主類載入到 (這裡以Math類為例)。

package com.tuling.jvm;
/**
 * @author Kang
 * @version 1.0
 */
public class Math {

    public static final int initData = 666;
    public static User user = new User () ;

    public int compute() { //一個 方法對應一塊棧幀記憶體區域
        int a = 1;
        int b = 2;
        int c=(a+b)*10;
        return c;
    }

    public static void main(String[] args) {
        Math math = new Math() ;
        math.compute();
        System.out.println("test");
    }

}

透過Java命令執行程式碼的大體流程如下(涉及類載入器下面做介紹):
在這裡插入圖片描述
我們載入Math類是要經過loadClass的類載入過程有如下幾步:

在這裡插入圖片描述

  • 載入 >> 驗證 >> 準備 >> 解析 >> 初始化 >> 使用 >> 解除安裝
    1. 載入:在硬碟上查詢並透過IO讀入位元組碼檔案,使用到類時才會載入,例如呼叫類的main()方法,new物件等等,在載入階段會在記憶體中生成一個代表這個類的java.lang.Class物件,作為方法區這個類的各種資料的訪問入口
    2. 驗證:校驗位元組碼檔案的正確性
    3. 準備:給類的靜態變數分配記憶體,並賦予預設值
      解析:將符號引用替換為直接引用,該階段會把一些靜態方法(符號引用,比如main()方法)替換為指向資料所存記憶體的指標或控制代碼等(直接引用),這是所謂的靜態連結過程(類載入期間完成),動態連結是在程式執行期間完成的將符號引用替換為直接引用。
    4. 初始化:對類的靜態變數初始化為指定的值,執行靜態程式碼塊

二、類載入器和雙親委派機制

1.上面的類載入過程主要是透過類載入器來實現的,Java裡有如下幾種類載入器

  • 引導類載入器:負責載入支撐JVM執行的位於JRE的lib目錄下的核心類庫,比如 rt.jar、charsets.jar等
  • 擴充套件類載入器:負責載入支撐JVM執行的位於JRE的lib目錄下的ext擴充套件目錄中的JAR 類包
  • 應用程式類載入器:負責載入ClassPath路徑下的類包,主要就是載入你自己寫的那 些類
  • 自定義載入器:負責載入使用者自定義路徑下的類包

JVM類載入器是有親子層級結構的,如下圖
在這裡插入圖片描述
這裡類載入其實就有一個雙親委派機制,載入某個類時會先委託父載入器尋找目標類,找不到再 委託上層父載入器載入,如果所有父載入器在自己的載入類路徑下都找不到目標類,則在自己的 類載入路徑中查詢並載入目標類。

比如我們的Math類,最先會找應用程式類載入器載入,應用程式類載入器會先委託擴充套件類載入 器載入,擴充套件類載入器再委託引導類載入器,頂層引導類載入器在自己的類載入路徑裡找了半天 沒找到Math類,則向下退回載入Math類的請求,擴充套件類載入器收到回覆就自己載入,在自己的 類載入路徑裡找了半天也沒找到Math類,又向下退回Math類的載入請求給應用程式類載入器, 應用程式類載入器於是在自己的類載入路徑裡找Math類,結果找到了就自己載入了。。

雙親委派機制說簡單點就是,先找父親載入,不行再由兒子自己載入

2.類載入器初始化過程?

參見類執行載入全過程圖可知其中會建立JVM啟動器例項sun.misc.Launcher。可以用程式設計工具全域性搜尋這個Launcher類
在Launcher構造方法內部,其建立了兩個類載入器,分別是 sun.misc.Launcher.ExtClassLoader(擴充套件類載入器)和,sun.misc.Launcher.AppClassLoader(應 用類載入器)。 JVM預設使用Launcher的getClassLoader()方法返回的類載入器AppClassLoader的例項載入我們 的應用程式。
構造方法如下:

//Launcher的構造方法
public Launcher() {
       Launcher.ExtClassLoader var1;
       try {
       		//構造擴充套件類載入器,在構造的過程中將其父載入器設定為null
           var1 = 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);
       String var2 = System.getProperty(".security.manager");
       if (var2 != null) {
           SecurityManager var3 = null;
           if (!"".equals(var2) && !"default".equals(var2)) {
               try {
                   var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
               } catch (IllegalAccessException var5) {
               } catch (InstantiationException var6) {
               } catch (ClassNotFoundException var7) {
               } catch (ClassCastException var8) {
               }
           } else {
               var3 = new SecurityManager();
           }

           if (var3 == null) {
               throw new InternalError("Could not create SecurityManager: " + var2);
           }

           System.setSecurityManager(var3);
       }

   }

我們來看下應用程式類載入器AppClassLoader載入類的雙親委派機制原始碼,AppClassLoader 的loadClass方法最終會呼叫其父類ClassLoader的loadClass方法,該方法的大體邏輯如下:

  1. 首先,檢查一下指定名稱的類是否已經載入過,如果載入過了,就不需要再載入,直接 返回。
  2. 如果此類沒有載入過,那麼,再判斷一下是否有父載入器;如果有父載入器,則由父加 載器載入(即呼叫parent.loadClass(name, false);).或者是呼叫bootstrap類載入器來加 載。
  3. 如果父載入器及bootstrap類載入器都沒有找到指定的類,那麼呼叫當前類載入器的 findClass方法來完成類載入。
 //ClassLoader的loadClass方法,裡面實現了雙親委派機制
protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 檢查當前類載入器是否已經載入了該類
            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) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                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 stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {  //不會執行
                resolveClass(c);
            }
            return c;
        }
    }

3.為什麼要設計雙親委派機制?

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

4.全盤負責委託機制

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

總結

因為是第一次寫部落格,如有諸多不好,還望包容。

本文章已修改原文用詞符合繁體字使用者習慣使其容易閱讀

版權宣告:此處為CSDN博主「奮鬥的小付」的原創文章,依據CC 4.0 BY-SA版權協議,轉載請附上原文出處連結及本宣告。

原文連結:https://blog.csdn.net/qq_49281137/article/details/109120019