91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

如何理解JVM中類加載與字節碼技術(類加載與類的加載器)

發布時間:2021-06-15 17:14:21 來源:億速云 閱讀:129 作者:chen 欄目:開發技術

本篇內容介紹了“如何理解JVM中類加載與字節碼技術(類加載與類的加載器)”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!

1. 類加載階段

1.1 加載階段

  • 將類的字節碼載入方法區中,內部采用 C++ 的 instanceKlass 描述 java 類,它的重要 field 有:

    • _java_mirror 即 java 的類鏡像,例如對 String 來說,就是 String.class,作用是把 klass 暴 露給 java 使用

    • _super 即父類

    • _fields 即成員變量

    • _methods 即方法

    • _constants 即常量池

    • _class_loader 即類加載器

    • _vtable 虛方法表

    • _itable 接口方法表

  • 如果這個類還有父類沒有加載,則先觸發父類的加載。

  • 加載和鏈接可能是交替運行的。

注意:

  • instanceKlass 這樣的【元數據】是存儲在方法區(1.8 后的元空間內),但 _java_mirror 是存儲在堆中

  • 可以通過前面介紹的 HSDB 工具查看

如何理解JVM中類加載與字節碼技術(類加載與類的加載器)

1.2 鏈接階段

驗證

驗證類是否符合 JVM規范,安全性檢查,阻止不合法的類繼續運行。用 UE 等支持二進制的編輯器修改 HelloWorld.class的魔數,在控制臺運行:

E:\git\jvm\out\production\jvm>java cn.itcast.jvm.t5.HelloWorld
Error: A JNI error has occurred, please check your installation and try again
Exception in thread "main" java.lang.ClassFormatError: Incompatible magic value
3405691578 in class file cn/itcast/jvm/t5/HelloWorld
        at java.lang.ClassLoader.defineClass1(Native Method)
        at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
        at
java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
        at java.net.URLClassLoader.defineClass(URLClassLoader.java:467)
        at java.net.URLClassLoader.access$100(URLClassLoader.java:73)
        at java.net.URLClassLoader$1.run(URLClassLoader.java:368)
        at java.net.URLClassLoader$1.run(URLClassLoader.java:362)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(URLClassLoader.java:361)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
        at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:495)
準備

為 static 變量分配空間,設置默認值:

  • static 變量在 JDK 7 之前存儲于 instanceKlass 末尾,從 JDK 7 開始,存儲于 _java_mirror 末尾

  • static 變量分配空間和賦值是兩個步驟,分配空間在準備階段完成,賦值在初始化階段完成

  • 如果 static 變量是 final 的基本類型,以及字符串常量,那么編譯階段值就確定了,賦值在準備階 段完成

  • 如果 static 變量是 final 的,但屬于引用類型,那么賦值也會在初始化階段完成

  • 將常量池中的符號引用解析為直接引用

解析

將常量池中的符號引用解析為直接引用

/**
* 解析的含義
*/
public class Load2 {
    public static void main(String[] args) throws ClassNotFoundException,IOException {
        ClassLoader classloader = Load2.class.getClassLoader();
        // loadClass 方法不會導致類的解析和初始化
        Class<?> c = classloader.loadClass("cn.itcast.jvm.t3.load.C");
        // new C();
        System.in.read();
    }
}
class C {
	D d = new D();
}
class D {
}

1.3 初始化階段

< init()> V 方法

初始化即調用 < cinit>()V ,虛擬機會保證這個類的『構造方法』的線程安全。

發生的時機

概括得說,類初始化是【懶惰的】

  • main 方法所在的類,總會被首先初始化

  • 首次訪問這個類的靜態變量或靜態方法時

  • 子類初始化,如果父類還沒初始化,會引發

  • 子類訪問父類的靜態變量,只會觸發父類的初始化

  • Class.forName

  • new 會導致初始化

不會導致類初始化的情況:

  • 訪問類的 static final 靜態常量(基本類型和字符串)不會觸發初始化

  • 類對象.class 不會觸發初始化

  • 創建該類的數組不會觸發初始化

  • 類加載器的 loadClass 方法

測試代碼:

class A {
    static int a = 0;
    static {
    	System.out.println("a init");
    }
}
class B extends A {
    final static double b = 5.0;
    static boolean c = false;
    static {
    	System.out.println("b init");
    }
}

驗證(測試時請先全部注釋,每次只執行其中一個)

public class Load3 {
    // main方法的所在類總會被先初始化
    static {
    	System.out.println("main init");
    }
    public static void main(String[] args) throws ClassNotFoundException {
        // 1. 靜態常量(基本類型和字符串)不會觸發初始化
        System.out.println(B.b);
        // 2. 類對象.class 不會觸發初始化
        System.out.println(B.class);
        // 3. 創建該類的數組不會觸發初始化
        System.out.println(new B[0]);
        // 4. 不會初始化類 B,但會加載 B、A
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        cl.loadClass("cn.itcast.jvm.t3.B");
        // 5. 不會初始化類 B,但會加載 B、A
        ClassLoader c2 = Thread.currentThread().getContextClassLoader();
        Class.forName("cn.itcast.jvm.t3.B", false, c2);
        // 1. 首次訪問這個類的靜態變量或靜態方法時
        System.out.println(A.a);
        // 2. 子類初始化,如果父類還沒初始化,會引發
        System.out.println(B.c);
        // 3. 子類訪問父類靜態變量,只觸發父類初始化
        System.out.println(B.a);
        // 4. 會初始化類 B,并先初始化類 A
        Class.forName("cn.itcast.jvm.t3.B");
    }
}

1.4 練習

從字節碼分析,使用 a,b,c 這三個常量是否會導致 E 初始化:

public class Load4 {
    public static void main(String[] args) {
        System.out.println(E.a);
        System.out.println(E.b);
        System.out.println(E.c);
    }
}
class E {
    public static final int a = 10;
    public static final String b = "hello";
    public static final Integer c = 20;
}

典型應用 - 完成懶惰初始化單例模式:

public final class Singleton {
    private Singleton() { }
    // 內部類中保存單例
    private static class LazyHolder {
        static final Singleton INSTANCE = new Singleton();
    }
    // 第一次調用 getInstance 方法,才會導致內部類加載和初始化其靜態成員
    public static Singleton getInstance() {
    	return LazyHolder.INSTANCE;
    }
}

以上的實現特點是:

  • 懶惰實例化

  • 初始化時的線程安全是有保障的

2. 類加載器

以 JDK 8 為例:

名稱加載哪的類說明
Bootstrap ClassLoader(啟動類加載器)JAVA_HOME/jre/lib無法直接訪問
Extension ClassLoader(擴展類加載器)JAVA_HOME/jre/lib/ext上級為 Bootstrap,顯示為 null
Application ClassLoader(應用程序類加載器)classpath上級為 Extension
自定義類加載器自定義上級為 Application

類加載器的優先級(由高到低):啟動類加載器 -> 擴展類加載器 -> 應用程序類加載器 -> 自定義類加載器

2.1 啟動類加載器

用 Bootstrap 類加載器加載類:

package cn.itcast.jvm.t3.load;
public class F {
    static {
   		System.out.println("bootstrap F init");
    }
}

執行:

package cn.itcast.jvm.t3.load;
public class Load5_1 {
    public static void main(String[] args) throws ClassNotFoundException {
        Class<?> aClass = Class.forName("cn.itcast.jvm.t3.load.F");
        // aClass.getClassLoader():獲得aClass對應的類加載器
        System.out.println(aClass.getClassLoader());
    }
}

輸出:

  • -Xbootclasspath 表示設置 bootclasspath

  • 其中 /a:. 表示將當前目錄追加至 bootclasspath 之后

  • 可以有以下幾個方式替換啟動類路徑下的核心類:

    • java -Xbootclasspath: < new bootclasspath>

    • 前追加:java -Xbootclasspath/a:<追加路徑>

    • 后追加:java -Xbootclasspath/p:<追加路徑>

如何理解JVM中類加載與字節碼技術(類加載與類的加載器)

2.2 擴展類加載器

package cn.itcast.jvm.t3.load;
public class G {
    static {
    	System.out.println("classpath G init");
    }
}

程序執行:

public class Load5_2 {
    public static void main(String[] args) throws ClassNotFoundException {
        Class<?> aClass = Class.forName("cn.itcast.jvm.t3.load.G");
        System.out.println(aClass.getClassLoader());
    }
}

輸出結果:

classpath G init
sun.misc.Launcher$AppClassLoader@18b4aac2 // 這個類是由應用程序加載器加載

寫一個同名的類:

package cn.itcast.jvm.t3.load;
public class G {
    static {
    	System.out.println("ext G init");
    }
}

打個 jar 包:

E:\git\jvm\out\production\jvm>jar -cvf my.jar cn/itcast/jvm/t3/load/G.class // 將G.class打jar包
已添加清單
正在添加: cn/itcast/jvm/t3/load/G.class(輸入 = 481) (輸出 = 322)(壓縮了 33%)

將 jar 包拷貝到JAVA_HOME/jre/lib/ext(擴展類加載器加載的類必須是以jar包方式存在),重新執行 Load5_2

輸出:

ext G init
sun.misc.Launcher$ExtClassLoader@29453f44 // 這個類是由擴展類加載器加載

2.3 雙親委派模式

所謂的雙親委派,就是指調用類加載器的 loadClass 方法時,查找類的規則。

注意:這里的雙親,翻譯為上級似乎更為合適,因為它們并沒有繼承關系

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    synchronized (getClassLoadingLock(name)) {
        // 1. 檢查該類是否已經加載
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                    // 2. 有上級的話,委派上級 loadClass
                    c = parent.loadClass(name, false);
                } else {
                    // 3. 如果沒有上級了(ExtClassLoader),則委派
                    BootstrapClassLoader
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
            }
            if (c == null) {
                long t1 = System.nanoTime();
                // 4. 每一層找不到,調用 findClass 方法(每個類加載器自己擴展)來加載
                c = findClass(name);
                // 5. 記錄耗時
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
        	resolveClass(c);
        }
        return c;
    }
}

例如:

public class Load5_3 {
    public static void main(String[] args) throws ClassNotFoundException {
        Class<?> aClass = Load5_3.class.getClassLoader()
        			.loadClass("cn.itcast.jvm.t3.load.H");
        System.out.println(aClass.getClassLoader());
    }
}

執行流程為:

  • sun.misc.Launcher$AppClassLoader // 1 處, 開始查看已加載的類,結果沒有

  • sun.misc.Launcher$AppClassLoader // 2 處,委派上級 sun.misc.Launcher$ExtClassLoader.loadClass()

  • sun.misc.Launcher$ExtClassLoader // 1 處,查看已加載的類,結果沒有

  • sun.misc.Launcher$ExtClassLoader // 3 處,沒有上級了,則委派 BootstrapClassLoader 查找

  • BootstrapClassLoader 是在 JAVA_HOME/jre/lib 下找 H 這個類,顯然沒有

  • sun.misc.Launcher$ExtClassLoader // 4 處,調用自己的 findClass 方法,是在JAVA_HOME/jre/lib/ext 下找 H 這個類,顯然沒有,回到 sun.misc.Launcher$AppClassLoader 的 // 2 處

  • 繼續執行到 sun.misc.Launcher$AppClassLoader // 4 處,調用它自己的 findClass 方法,在 classpath 下查找,找到了

2.4 線程上下文類加載器

我們在使用 JDBC 時,都需要加載 Driver 驅動,不知道你注意到沒有,不寫

Class.forName("com.mysql.jdbc.Driver")

也是可以讓 com.mysql.jdbc.Driver 正確加載的,你知道是怎么做的嗎? 讓我們追蹤一下源碼:

public class DriverManager {
    // 注冊驅動的集合
    private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers 
        = new CopyOnWriteArrayList<>();
    // 初始化驅動
    static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }

先不看別的,看看 DriverManager 的類加載器:

System.out.println(DriverManager.class.getClassLoader());

打印 null,表示它的類加載器是 Bootstrap ClassLoader,會到 JAVA_HOME/jre/lib 下搜索類,但 JAVA_HOME/jre/lib 下顯然沒有 mysql-connector-java-5.1.47.jar 包,這樣問題來了,在 DriverManager 的靜態代碼塊中,怎么能正確加載 com.mysql.jdbc.Driver 呢?

繼續看 loadInitialDrivers() 方法:

private static void loadInitialDrivers() {
    String drivers;
    try {
        drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
            public String run() {
            	return System.getProperty("jdbc.drivers");
            }
        });
    } catch (Exception ex) {
    	drivers = null;
    }
    // 1)使用 ServiceLoader 機制加載驅動,即 SPI
    AccessController.doPrivileged(new PrivilegedAction<Void>() {
    	public Void run() {
            ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
            Iterator<Driver> driversIterator = loadedDrivers.iterator();
            try{
                while(driversIterator.hasNext()) {
                    driversIterator.next();
                }
            } catch(Throwable t) {
                // Do nothing
            }
            return null;
        }
    });
    println("DriverManager.initialize: jdbc.drivers = " + drivers);
    // 2)使用 jdbc.drivers 定義的驅動名加載驅動
    if (drivers == null || drivers.equals("")) {
    	return;
    }
    String[] driversList = drivers.split(":");
    println("number of Drivers:" + driversList.length);
    for (String aDriver : driversList) {
        try {
            println("DriverManager.Initialize: loading " + aDriver);
            // 這里的 ClassLoader.getSystemClassLoader() 就是應用程序類加載器
            Class.forName(aDriver, true, ClassLoader.getSystemClassLoader());
        } catch (Exception ex) {
        	println("DriverManager.Initialize: load failed: " + ex);
        }
    }
}

先看 2)發現它最后是使用 Class.forName 完成類的加載和初始化,關聯的是應用程序類加載器,因此 可以順利完成類加載

再看 1)它就是大名鼎鼎的 Service Provider Interface (SPI)

約定如下,在 jar 包的 META-INF/services 包下,以接口全限定名名為文件,文件內容是實現類名稱

如何理解JVM中類加載與字節碼技術(類加載與類的加載器)

這樣就可以使用:

ServiceLoader<接口類型> allImpls = ServiceLoader.load(接口類型.class);
Iterator<接口類型> iter = allImpls.iterator();
while(iter.hasNext()) {
	iter.next();
}

來得到實現類,體現的是【面向接口編程+解耦】的思想,在下面一些框架中都運用了此思想:

  • JDBC

  • Servlet 初始化器

  • Spring 容器

  • Dubbo(對 SPI 進行了擴展)

接著看 ServiceLoader.load 方法:

public static <S> ServiceLoader<S> load(Class<S> service) {
    // 獲取線程上下文類加載器
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return ServiceLoader.load(service, cl);
}

線程上下文類加載器是當前線程使用的類加載器,默認就是應用程序類加載器,它內部又是由 Class.forName 調用了線程上下文類加載器完成類加載,具體代碼在 ServiceLoader 的內部類 LazyIterator 中:

private S nextService() {
    if (!hasNextService())
    	throw new NoSuchElementException();
    String cn = nextName;
    nextName = null;
    Class<?> c = null;
    try {
    	c = Class.forName(cn, false, loader);
    } catch (ClassNotFoundException x) {
        fail(service, "Provider " + cn + " not found");
    }
    if (!service.isAssignableFrom(c)) {
        fail(service, "Provider " + cn + " not a subtype");
    }
    try {
        S p = service.cast(c.newInstance());
        providers.put(cn, p);
        return p;
    } catch (Throwable x) {
        fail(service, "Provider " + cn + " could not be instantiated", x);
    }
    throw new Error(); // This cannot happen
}

2.5 自定義類加載器

問問自己,什么時候需要自定義類加載器:

  • 1)想加載非 classpath 隨意路徑中的類文件

  • 2)都是通過接口來使用實現,希望解耦時,常用在框架設計

  • 3)這些類希望予以隔離,不同應用的同名類都可以加載,不沖突,常見于 tomcat 容器

步驟:

  • 繼承 ClassLoader 父類

  • 要遵從雙親委派機制,重寫 findClass 方法 注意不是重寫 loadClass 方法,否則不會走雙親委派機制

  • 讀取類文件的字節碼

  • 調用父類的 defineClass 方法來加載類

  • 使用者調用該類加載器的 loadClass 方法

“如何理解JVM中類加載與字節碼技術(類加載與類的加載器)”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

花莲市| 西吉县| 额敏县| 安阳市| 罗定市| 拜泉县| 奇台县| 洮南市| 贡山| 邹平县| 昭苏县| 南昌县| 桂阳县| 庄浪县| 辽宁省| 华安县| 宜黄县| 浮梁县| 怀柔区| 富裕县| 平安县| 深圳市| 新宁县| 林口县| 井冈山市| 当涂县| 五河县| 玛纳斯县| 改则县| 宁德市| 万全县| 灌阳县| 枣庄市| 贞丰县| 中超| 岳阳市| 永登县| 阳谷县| 屯昌县| 广平县| 瓦房店市|