p7付費課程筆記:jvm基礎知識、字節碼、類加載器

你可以叫我老白 發佈 2023-12-04T14:03:52.053156+00:00

程式語言演化:機器語言->程式語言->高級語言(java,c++,Go,Rust等)面向過程--面向對象-面向函數java是一種面向對象、靜態類型、編譯執行,有VM(虛擬機)/GC和運行時、跨平台的高級語言。

程式語言

演化:

機器語言->程式語言->高級語言(JAVA,c++,Go,Rust等)

面向過程--面向對象-面向函數

java是一種面向對象、靜態類型、編譯執行,有VM(虛擬機)/GC和運行時、跨平台的高級語言。重點:VM(虛擬機)/GC(Garbage Collector)和運行時、跨平台。



跨平台步驟字節碼文件被虛擬機加載(類加載器)加載到內存中,轉換成具體的對象



字節碼

結構:

Java byteCode由單字節(byte)指令構成,理論上最多支持256個操作碼(opcode)。實際上java只使用了200左右的操作碼,其他留給了調試操作。

根據指令的性質大概分為四大類:

1.棧操作指令,包括與局部變量交互的指令,

2.程序流程指令,

3.對象操作指令,比如方法調用的指令,

4.算數運算以及類型轉換的指令,

運行步驟:



JVM是一個基於棧的計算機,每個線程都有獨屬於自己的線程棧(JVM Stack),用語存儲棧幀。每次調用方法就會自動創建一個線程棧。棧幀是由操作數棧、局部變量表以及一個class引用組成,class引用中又包含著我們使用的常量池

操作demo:
https://juejin.cn/post/7141206840456511496/


類加載器



類生命周期的七個步驟:

1.加載:找到class文件;

2.驗證:驗證字節碼文件格式是否正確、依賴是否完備;

3.準備:靜態欄位、方法表;

4.解析:符合解析為引用;

5.初始化:構造器,靜態變量賦值,靜態代碼塊;

6.使用

7.卸載

前五步是我們通常所說的類加載過程,其中2、3、4可以合在一起稱為-連結:

1 找到class文件,讀出來
2 驗證格式,解析欄位方法,所有符號轉化為實際引用
3 類相關初始化

類的加載時機:

虛擬機規範中並沒有強制約束何時進行加載,但是規範嚴格規定了有且只有下列五種情況必須對類進行初始化(加載、驗證、準備都會隨著發生):

1.3.1 遇到 new、getstatic、putstatic、invokestatic 這四條字節碼指令時,如果類沒有進行過初始化,則必須先觸發其初始化。最常見的生成這 4 條指令的場景是:使用 new 關鍵字實例化對象的時候;讀取或設置一個類的靜態欄位(被 final 修飾、已在編譯器把結果放入常量池的靜態欄位除外)的時候;以及調用一個類的靜態方法的時候。

1.3.2 使用 java.lang.reflect 包的方法對類進行反射調用的時候,如果類沒有進行初始化,則需要先觸發其初始化。

1.3.3當初始化一個類的時候,如果發現其父類還沒有進行過初始化,則需要先觸發其父類的初始化。

1.3.4 當虛擬機啟動時,用戶需要指定一個要執行的主類(包含 main() 方法的那個類),虛擬機會先初始化這個主類;

1.3.5 當使用 JDK.7 的動態語言支持時,如果一個
java.lang.invoke.MethodHandle 實例最後的解析結果為 REF_getStatic, REF_putStatic, REF_invokeStatic 的方法句柄,並且這個方法句柄所對應的類沒有進行過初始化,則需要先觸發其初始化;

以上 5 種場景中的行為稱為對一個類進行主動引用。除此之外,所有引用類的方式都不會觸發初始化,稱為被動引用。被動引用的常見例子包括:

通過子類引用父類的靜態欄位,不會導致子類初始化。

通過數組定義來引用類,不會觸發此類的初始化。該過程會對數組類進行初始化,數組類是一個由虛擬機自動生成的、直接繼承自 Object 的子類,其中包含了數組的屬性和方法。

常量在編譯階段會存入調用類的常量池中,本質上並沒有直接引用到定義常量的類,因此不會觸發定義常量的類的初始化。

總結:顯式,隱式
隱式,子類父類,實現類和接口,反射,動態調用
顯式,main方法,new,靜態欄位和方法


類加載器和特點


1.啟動類加載器(BootstrapClass Loader)

  • 這個類加載使用C/C++語言實現,嵌套在JVM內部
  • 它用來加載JAVA的核心庫(JAVA_HOME/jre/lib/rt.JAR,resources.jar或sun.boot.class.path路徑下的內容),用於提供JVM自身需要的類
  • 並不繼承自Java.lang.ClassLoader,沒有父加載器
  • 加載擴展類和應用程式類加載器,並指定為它們的父類加載器
  • 出於安全考慮,Bootstrap啟動類加載器只加載包名為java,javax,sun等開頭的類
  • 啟動類加載器不像其他類加載器有實體,它是沒有實體的,JVM將C++處理類加載的一套邏輯定義為啟動類加載器。因此,啟動類加載器是無法被Java程序調用的。


2.擴展類加載器(Extension Class Loader)

  • java語言編寫,由sun.misc.Launcher$ExtClassLoader實現
  • 派生於ClassLoader類
  • 父類加載器為啟動類加載器
  • 從Java.ext.dirs系統屬性所指的目錄中加載類庫,或從JDK的安裝目錄的jre/lib/ext子目錄(擴展目錄)下加載類庫。如果用戶創建的JAR放在此目錄下,也會自動由擴展類加載器加載。
public static void main(String[] args) {
    ClassLoader classLoader = ClassLoader.getSystemClassLoader().getParent();

    URLClassLoader urlClassLoader = (URLClassLoader) classLoader;

    URL[] urls = urlClassLoader.getURLs();
    for (URL url : urls) {
        System.out.println(url);
    }
}

3.應用程式加載器(系統類加載器,System Class Loader/App Class Loader)

  • java語言編寫,由sun.misc.Launcher&AppClassLoader實現
  • 派生於ClassLoader類
  • 父類加載器為擴展類加載器
  • 它負責加載環境變量classpath或系統屬性java.class.path指定路徑下的類庫
  • 該類加載是程序中默認的類加載器,一般來說,Java應用的類都是由它來完成加載
  • 通過ClassLoader#getSystemClassLoader()方法可以獲得到該類加載器
public static void main(String[] args) {
    String[] urls = System.getProperty("java.class.path").split(":");

    for (String url : urls) {
        System.out.println(url);
    }

    System.out.println("================================");

    URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();

    URL[] urls1 = classLoader.getURLs();
    for (URL url : urls1) {
        System.out.println(url);
    }
}


4.用戶自定義類加載器

在Java的日常應用程式開發中,類加載幾乎是由上述3種類加載器相互配合執行的,在必要時,我們還可以自定義類加載器,來定製類的加載方式。

1、開發人員可以通過繼承抽象類java.lang.classLoader類的方式,實現自己的類加載器,以滿足一些特殊的需求

2、在JDK2.0之前,在自定義類加載器時,總會去繼承classLoader類並重寫loadclass ()方法,從而實現自定義的類加載類,但是在JDK2.0之後已不再建議用戶去覆蓋loadclass ()方法,而是建議把自定義的類加載邏輯寫在findclass ()方法中

3、在編寫自定義類加載器時,如果沒有太過於複雜的需求,可以直接繼承URLClassLoader類,這樣就可以避免自己去編寫findclass ()方法及其獲取字節碼流的方式,這樣會讓自定義類加載器編寫更為簡單一些。


雙親委派

雙親委派機制的原理:

如果一個類加載器收到了類加載請求,它並不會自己先去加載,而是把這個請求委託給父類的加載器去執行。

如果父類的加載器還存在其父類加載器,則進一步向上委託,依次遞歸請求最終達到頂層的啟動類加載器。

如果父類加載器可以完成類加載任務,就成功返回,倘若父類加載器無法完成此加載任務,子加載器才會嘗試自己去加載,這就是雙親委派機制。

優點:

避免類的重複加載,確保一個類的全局唯一性

保護程序安全,防止核心API被隨意篡改

缺點:

  • 無法做到不委派
  • 無法做到向下委派

在某些場景下雙親委派制過於局限,所以有時候必須打破雙親委派機制來達到目的。例如:SPI機制,這個SPI機制涉及到打破雙親委派機制,工作中沒有涉及到就不細說了,感興趣的同學可以自己研究下。

雙親委派在JVM中的實現代碼:

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) {
                    // this 是AppClassLoader, this.parent是ExtClassLoader
                    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();
                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;
    }
}


這一期的課程大概就講了這麼多吧,說實話看完還是好多記不住和不理解,也是反覆記憶並且查了好多資料才知道,所以不理解很正常,沒有接觸過就能一遍看懂的一般都是高級及以上了,慢慢看就可以了。看一點就是進步。

下期明天寫,大概是內存模型和JMM的相關知識,小夥伴可以先複習下,然後查漏補缺。

視頻

因為百度雲上傳視頻需要用會員,所以使用了阿里雲。沒有條件的小夥伴可以私信我,單發。 阿里雲盤視頻連結:https://www.aliyundrive.com/s/LqvyyqsDgyS

今天就到這裡吧,感覺有用的小夥伴可以點個讚,你的支持就是我更新的最大動力!

關鍵字: