本篇文章就帶給大家java學習之Jvm類別的載入機制介紹。有一定的參考價值,有需要的朋友可以參考一下,希望對你們有幫助。
虛擬機載入Class檔案(二進位位元組流)到內存,並對資料進行校驗、轉換解析和初始化,最終形成可被虛擬機直接使用的Java類型,這一系列過程就是類別的載入機制。
類別從被虛擬機器載入到記憶體開始,直到卸載出記憶體為止,整個生命週期包括:載入— —驗證-準備-解析-初始化-使用-卸載 這7個階段。其中驗證、準備、解析3個部分統稱為連線。
生命週期圖如下:
其中載入、驗證、準備、初始化、卸載這5個階段順序是確定的,類別的載入過程必須按照這種順序進行開始,而解析階段則不一定:它在某種情況下可以在初始化之後再開始,這也是為了支援Java語言的動態綁定。
遇到new、getstatic、putstatic、invokestatic 這4個指令時如果類別沒有初始化則會觸發其初始化, (工作中觸發這4種指令最常見的場景:new實例化物件、讀取or設定類別的靜態欄位【final修飾或已經把靜態欄位放入常數池的除外】、呼叫類別的靜態方法)
使用反射的時候
初始化類別的時候如果其父類別還沒進行初始化,則需要先觸發父類別的初始化
虛擬機器啟動時,需要指定一個要執行的主類別(包含main方法的那個類別),虛擬機會先初始化這個類別
使用jdk1.7動態語言支援時,如果一個java.lang.invoke.MethodHandle實例最後的解析結果是REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄。切這個句柄對應的類別沒有初始化,則需要先觸發其初始化
注意:所有引用類別的方式都不會觸發初始化(被動引用)例如:建立陣列、引用final修飾的變數、子類別引用父類別的靜態變數不會觸發子類別初始化但是會觸發父類別初始化
載入是類別載入的一個階段,在載入階段 虛擬機器需要完成下面3件事情
透過類別的全限定名來取得定義此類的二進位位元組流
將這個位元組流所代表的靜態儲存結構轉換成方法區的運行時資料結構
#在記憶體中產生一個代表此類的java.lang.Object對象,作為方法區這個類別的各種資料的存取入口
相對於類別載入的其他階段,載入階段(準確的說,是載入階段中獲取類別的二進位位元組流的動作)是開發人員可控性最強的。因為載入階段既可以使用系統提供的引導類別載入器來完成,也可以由開發人員自訂的類別載入器來完成(即重寫類別載入器的loadClass()方法)。
載入完成後,外部的二進位位元組流就轉換成虛擬機器所需的格式儲存在方法區中,然後在記憶體中實例化一個java.lang.Class類別的物件。這個物件將作為程式存取方法區中的這些類型資料的外部介面。
載入階段與連線階段的部分內容是交叉進行的,並不是載入完成後才能執行驗證等操作。這些夾在載入之中的動作仍屬於連接階段的內容,這兩個階段的開始時間仍保持著固定的先後順序。
驗證是連線的第一步,為了確保載入的二進位位元組流所包含的資訊是符合虛擬機器規範的。
驗證階段大致分為下面4個檢驗動作:
檔案格式驗證:驗證位元組流是否符合Class檔案格式規格。例如:是否以魔鬼數 0xCAFEBABE 開頭、主次版本號是否在目前虛擬機器處理範圍內、常數池中的常數是否有不支援的類型······。
元資料驗證:對字節碼描述的資訊進行語意分析。例如: 這個類別是否有父類別、是否正確的繼承了父類別。
字節碼驗證:透過資料流和控制流的分析,確定程式語義是合法的、符合邏輯的(說白了就是對類別的方法體進行分析確保方法在運作時不會危害虛擬機器)。
符號參考驗證:確保解析動作能正常執行。
驗證階段是非常重要,但不一定是必要的階段(因為對程式運行期沒有影響)。如果所運行的全部程式碼都已經重複使用和驗證過,那麼在實施階段可以使用-Xverify:none參數來關閉驗證。
正式為類別變數分配記憶體並設定類別變數初始值。這些變數所使用的記憶體都會在方法區中進行分配。
注意:
此時被分配的只是靜態變量,而不是實例變量,實例變數將隨著物件實例一起分配在Java堆中
初始值通常情況下是資料類型的零值。假如定義一個靜態變數 public static int value = 123;那麼value在準備階段初始值為0而不是123。
被final修飾的變數在準備階段就初始化為屬性所指定的值。 例如: public static final int value = 123;那麼value在準備階段初始值就是123。
解析階段是虛擬機器將常數池內的符號引用替換為直接引用的過程。解析動作主要針對類別或介面、欄位、類別方法、介面方法、方法類型、方法句柄和呼叫點限定符7類符號參考進行。
符號引用:以一組符號來描述引用的目標,符號可以是任何形式的字面量。
直接引用:指向目標的指標、相對偏移量或是一個能間接定位到目標的句柄。
初始化階段是執行類別建構器
類別建構器
編譯器收集的順序是由語句在原始檔中出現的順序決定的;靜態程式碼區塊只能存取定義在靜態區塊之前的變量,定義在它之後的變量,在前面的靜態區塊中可以賦值,但不能存取。
非法向前引用示例 public class SuperClass { public static int va; static { value = 1; //可以编译通过 va = value; //报错 非法向前引用 System.out.println("父类初始化"); } public static int value = 123; }
介面中不能使用靜態區塊,但仍可以有變數賦值運算,因此介面和類別一樣都會產生
虛擬機會保證一個類別的
虛擬機器設計團隊把類別載入中的「透過一個類別的全限定名稱來取得描述此類的二進位位元組流」這個動作放到Java虛擬機器外部去實現,以便讓應用程式自己決定如何去取得所需的類別。實作這個動作的程式碼區塊稱為類別載入器。
啟動類別載入器(Bootstrap Classloader):負責將存放在< JAVA_HOME>\lib(Javahome即jdk的安裝目錄)目錄中的,或者被-Xbootclasspath參數所指定的路徑中的,並且是虛擬機器識別的(僅按照文件名識別,如rt.jar,名字不符合的類別庫即使放在lib下面也不會被載入)類別庫載入到虛擬機器記憶體中。 啟動類別載入器無法被Java程式直接使用。
擴充類別載入器(Extension Classloader):這個載入器由sun.misc.Launcher$ExtClassLoader實現,它負責載入
應用程式類別載入器(Application Classloader):此載入器由sun.misc.Launcher$AppClassLoader實現,它負責載入使用者類別路徑(ClassPath)上所指定的類別庫。開發者可以直接使用此載入器。如果應用程式中沒有自訂的類別載入器,那麼這個就是程式預設執行的類別載入器。 (系統載入器)
我们的应用程序都是由这3种类加载器相互配合进行加载的。如果有必要,还可以加入自定义的类加载器。
这些类加载器之间的关系如下图:
双亲委派模型的工作过程是:如果一个类加载器收到了一个类加载请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一层的加载器都是如此,因此所有的加载请求最终都应该到达顶层的启动类加载器。只有当父加载无法完成这个加载请求时,子加载器才会尝试自己去加载。
1、当ApplicationClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。
2、当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。
3、如果BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;
4、若ExtClassLoader也加载失败,则会使用AppClassLoader来加载,如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException。
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(); 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; } }
系统类防止内存中出现多份同样的字节码
保证Java程序安全稳定运行
参考
《深入理解Java虚拟机》
总结:以上就是本篇文的全部内容,希望能对大家的学习有所帮助。更多相关教程请访问Java视频教程,java开发图文教程,bootstrap视频教程!
以上是java學習之Jvm類別的載入機制的詳細內容。更多資訊請關注PHP中文網其他相關文章!