JVM系列【3】Class文件加載過程

JVM系列筆記目錄

  • 虛擬機的基礎概念
  • class文件結構
  • class文件加載過程
  • jvm內存模型
  • JVM經常使用指令
  • GC與調優

Class文件加載過程

JVM加載Class文件主要分3個過程:Loading 、Linking、Initialzingjava

1.Loading

Loading的過程就是經過類加載器將.class文件加載到jvm內存中過程。須要理解雙親委派機制、類加載器ClassLoader,加載過程以下。
file設計模式

ClassLoader

不一樣的類加載器加載範圍不同,以Java8中的爲例。tomcat

  • BootClassLoader 加載範圍sun.boot.class.paht
  • ExtClassLoader 加載範圍java.ext.dirs
  • AppClassLoader 加載範圍java.class.path
  • CustomClassLoader 可自定義加載範圍

前三個加載器來自JDK的Launcher類,三個ClassLoader做爲Launcher的內部類,感興趣能夠查看下源碼。
file
開發者也能夠自定義的ClassLoader,自定義記載範圍。安全

雙親委派機制

自底向上檢查該類是否已經加載,parent方向;自頂向下進行類的實際查找和加載,child方向。
類的加載遵循雙親委派機制,主要是出於安全的考慮。雙親委派機制是如何實現的,下面源碼會解釋。
file
注意:雙親委派中存在所謂的父加載器並非加載器的加載器,只是翻譯的問題,別混淆了類的繼承概念。多線程

ClassLoader源碼

file
ClassLoader源碼中比較重要的一個函數是loadClass(),執行過程是:findLoadedClass()->parrent.loadClass()->findClass(),第一步是自底向上查詢是否已經加載,第二步是自頂向下查找加載類。這裏就規定或是說實現了雙親委派機制。詳細見ClassLoader的源碼。併發

自定義ClassLoader

如何自定義ClassLoader?能夠繼承ClassLoader類,從新本身的findClass(),在裏面調用defineClass()來實現自定義加載特定範圍的類。jvm

如何打破雙親委派機制,哪一種情形下打破過?

從上面的ClassLoader源碼中大概能看出是如何實現了雙親委派機制的,從這入手能夠經過2種方式打破該機制:函數

  1. super(parent)指定parent會打破該機制
  2. 自定義ClassLoader重寫loadClass()也能夠打破

什麼時候打破過?雙親委派機制並非不能打破,某些特殊場景下也會選擇打破該機制。線程

  1. JDK 1.2以前,自定義ClassLoader必須重寫loadClass(),打破過。
  2. 線程ThreadContextClassLoader能夠實現基礎類調用實現類代碼,經過thread.setContextClassLoader指定。
  3. 熱啓動熱部署,如tomcat都有本身模塊指定的classloader,能夠加載同一類庫的不一樣版本。

Class執行方式

Class執行方式分爲3種:解釋執行、編譯執行、混合執行,各有優缺點,可經過參數指定。翻譯

  • 1.解釋執行:使用bytecode intepreter 解釋器解釋執行,該模式啓動很快,執行稍慢,可經過-Xint參數指定該模式。
  • 2.編譯執行:使用 Just in time Complier JIT編譯器編譯執行,該模式執行很快,編譯很慢,可經過-Xcomp參數指定該模式。
  • 3.混合執行:默認的模式,解釋器+熱點代碼編譯,開始解釋執行,啓動較快,對熱點代碼進行實時監測和編譯成本地代碼執行,可經過-Xmixed參數指定該模式。

熱點代碼監測:屢次被調用的方法用方法計數器,屢次被調用的循環用循環計數器,可經過參數-XX:CompileThreshold = 10000指定觸發JIT編譯的閾值。

2.Linking

Linking連接的過程分3個階段:Vertification、Preparation、Resolution。

  • Vertification: 驗證Class文件是否符合JVM規定。
  • Preparation:給靜態成員變量賦默認值
  • Resolution:將類、方法、屬性等符號引用解釋爲直接引用;常量池中的各類符號引用解釋爲指針、偏移量等內存地址的直接引用

3. Initializing

調用初始化代碼clint,給靜態成員變量賦初始值。

這裏能夠了解下必須初始化的5種狀況:

  • new getstatic putstatic invokestatic指令,訪問final變量除外
  • java.lang.reflect對類進行反射調用時
  • 初始化子類的時候,父類必須初始化
  • 虛擬機啓動時,被執行的主類必須初始化
  • 動態語言支持java.lang.invoke.MethodHandler解釋的結果爲REF_getstatic REF_putstatic REF_invokestatic的方法句柄時,該類必須初始化。

4.總結思考

設計模式中單例模式的雙重檢查的實現,INSTANCE是否須要加valatile

public class Mgr06 {
    // 是否須要加volatile?
    private static volatile Mgr06 INSTANCE;

    private Mgr06() {
    }

    public static Mgr06 getInstance() {
        if (INSTANCE == null) {
            //雙重檢查
            synchronized (Mgr06.class) {
                if(INSTANCE == null) {
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // new 了對象,不爲null,但未完成變量的初始化複製,對象處於半初始化狀					態,其它線程有可能取到半初始化的對象。
                    INSTANCE = new Mgr06();
                }
            }
        }
        return INSTANCE;
    }
}

我的認爲是須要加的。思考方向, class文件load到內存,給靜態變量賦默認值,再賦初始值,new 對象的時候,首先要申請內存空間,而後給成員變量賦默認值,接下來給成員變量賦初始值,這個過程當中對象有可能處於半初始化狀態,多線程併發下別的線程有可能取到半初始化的對象,加volatile可保證線程的可見性。

知識分享,轉載請註明出處。學無前後,達者爲先!

相關文章
相關標籤/搜索