JVM——详解类加载过程
導航
- 一、過程概述
- 二、Loading
- 2.1 類加載器
- 2.2 雙親委派機制
- 2.3 類在內存中的結構
- 三、Linking
- 四、Initializing
一、過程概述
java 源文件編譯后會生成一個 .class文件存儲在硬盤上。
在程序運行時,會將用到的類文件加載到 JVM 內存中。從磁盤到內存的過程總共分為三個步驟:加載、連接、初始化。
二、Loading
Loading 過程是把一個 class 文件加載到內存中去。
2.1 類加載器
JVM 加載類的方式是按需動態加載,采用雙親委派機制。
認識幾種系統提供的類加載器:
Bootstrap 啟動類加載器,負責加載指定目錄——JAVA_HOME/lib下的 rt.jar、charset.jar 等核心類庫,由 C++實現,是JVM 的不可分割的一部分。類加載范圍可以在 Launcher 類中查看:
ExtClassLoader 擴展類加載器,負責加載指定目錄——JAVA_HOME/lib/ext 下的擴展包,或者也可以由 -Djava.ext.dirs 參數指定。類加載范圍可以在 Launcher 類中查看:
AppClassLoader 應用類加載器,加載用戶應用的 classpath 下的 class 文件,這是應用程序的默認類加載器,用戶自定義的類都是通過這個類加載器來加載。類加載范圍可以在 Launcher 中查看:
自定義類加載器,開發者自定義的 ClassLoader,繼承自 ClassLoader 抽象類,并重寫 findClass(…) 。
類加載器(除 C++實現的 Bootstrap 外)本身就是一個普通的 class,JVM 所有的 class 都是被類加載器加載到內存的。
public class TestLoadClass {public static void main(String[] args) {System.out.println(TestLoadClass.class.getClassLoader());System.out.println(TestLoadClass.class.getClassLoader().getParent());// Bootstrap 由 C++ 實現,Java 中沒有具體的類與之對應,故返回 nullSystem.out.println(TestLoadClass.class.getClassLoader().getParent().getParent());} } output: sun.misc.Launcher$AppClassLoader@58644d46 sun.misc.Launcher$ExtClassLoader@4554617c null2.2 雙親委派機制
JVM 加載類時處于安全考慮,基礎類和擴展類等,都必須由指定的類加載器來加載,不同的類加載器有自己的命名空間,同一個類,如果由不同的類加載器加載,會在內存中存在多份類對象。也正因如此,JVM 的類加載機制要求諸如 java.lang.Object 這種基礎類必須由最基礎的 Bootstrap 來加載。
因此,整個 Loading 的過程就成了:底層類加載器收到類加載請求后,必須將請求層層傳遞給父級加載器檢查,確認是否應該由父級加載器加載,若由于父加載器的指定類路徑中沒有該類文件,就會再層層向下返回,最終才會去加載:
Tip:注意,AppClassLoader 的父加載器是 ExtClassLoader,ExtClassLoader的父加載器是Bootstrap 加載器。
這里的父加載器并不是 Java 多態中語法的 extends 繼承關系,而是一種架構上的層級關系,AppClassLoader 和 ExtClassLoader 之間沒有任何繼承關系,它們在語法上,都繼承自 ClassLoader 抽象類,實際上 ClassLoader 中維護了一個 ClassLoader parent 引用,這才是 “雙親” 的真實面目,即 AppClassLoader 的父加載器實際上是其內部的一個組合對象。
2.3 類在內存中的結構
加載的時候,創建了兩塊內容,第一塊內容是把 Xxx.class 二進制扔到內存中,第二塊內容是生成一個 Class 類的對象,該對象中的變量會指向前一塊的實際內存地址(這一步實際上是Resolution 的過程)。
三、Linking
Linking,連接,這是一個大的步驟,其中又分為三個小步驟:
驗證:校驗 class 文件格式是否符合 jvm 規范,如開頭的魔數等。
準備:將 class 中的靜態變量賦默認值,所謂默認值,舉個例子,int 類型默認值是 0,String 類型默認值是 null。
解析:把class 文件常量池中的符號引用轉化為直接內存地址。
四、Initializing
初始化,區別于 Linking 中的 Preparation ,此過程將靜態變量賦初始值。比如:
public static int num = 8;這個 num 靜態變量會在 Preparation 階段賦值 0 ,在 Initializing 階段賦值 8。
Java初始化的時機,JVM規范中有明確規定,自然,加載也一定是在初始化之前完成。JVM規范中定義了以下這些必須初始化完畢的場景:
注意:對于 static final 類型的屬性,在編譯之后即存儲在字節碼文件的常量池中,直接引用它不會觸發初始化。
記憶技巧:
總結
以上是生活随笔為你收集整理的JVM——详解类加载过程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android JNI Attempt
- 下一篇: python圆面积函数_Python基础