類的生命周期 首先我們先看類的生命周期 類的載入過程包含了載入、驗證、準備、解析、初始這五個階段,其中除瞭解析階段其他四個階段的發生順序都是確定的,因為解析階段在某些情況下會在初始階段之後開始,同時這些階段都是按順序開始的不是按順序進行或結束,因為這些階段通常都是互相交叉的混合進行。以下為類的生命周 ...
類的生命周期
首先我們先看類的生命周期
類的載入過程包含了載入、驗證、準備、解析、初始這五個階段,其中除瞭解析階段其他四個階段的發生順序都是確定的,因為解析階段在某些情況下會在初始階段之後開始,同時這些階段都是按順序開始的不是按順序進行或結束,因為這些階段通常都是互相交叉的混合進行。以下為類的生命周期
載入->驗證->準備->解析->初始化->使用->卸載 (驗證->準備->解析這三個可概括為連接階段)
類載入器並不需要等到某個類被“首次主動使用”時再載入它,JVM規範允許類載入器在預料某個類將要被使用時就預先載入它,如果在預先載入的過程中遇到了.class文件缺失或存在錯誤,類載入器必須在程式首次主動使用該類時才報告錯誤(LinkageError錯誤)如果這個類一直沒有被程式主動使用,那麼類載入器就不會報告錯誤。
連接階段:
首先進行驗證,確保當前Class文件的位元組流信息是否符合當前虛擬機的要求分為:文件格式驗證、元數據驗證、位元組碼驗證、 符號引用驗證。然後進行準備階段開始正式為類的變數(僅包括類變數static,不包含實例變數,實例變數會在對象實例化時一起分配到Java堆中)分配記憶體並設置變數初始值(零值非代碼中的數值)的階段,這些記憶體在方法區中分配。開始解析,將常量池中的常量引用替換為直接引用。
初始化為類的靜態變數賦予正確的初始值。
初始化有以下步驟:如果這個類還沒有被載入和連接則先進行載入和連接;如果該類 的父類還沒有被初始化,則先初始化其父;如果該類中有初始化語句則先依次執行類中的初始化語句。
由此也可得出類的初始話時機,創建類的實例時(new);訪問某個類或介面的靜態變數或對該靜態變數賦值時;調用類的靜態方法時;反射(Class.forname("com.XXX.XXX"));初始化某個類的子類時;Java虛擬機啟動時被標記為啟動類時
使用:類訪問方法區內的數據結構的介面,對象時Heap區(堆區)的數據。
最後卸載階段:執行了System.exit()方法時;程式正常結束時;程式執行過程中遇到了異常或錯誤導致異常終止;由於操作系統出翔錯誤而導致java虛擬機進程終止;
類載入器、jvm載入機制:
再java程式員的眼中類載入器主要分為三種;
啟動類載入器: Bootstrap ClassLoader,負責載入存放在JDK\jre\lib(JDK代表JDK的安裝目錄,下同)下,或被-Xbootclasspath參數指定的路徑中的,並且能被虛擬機識別的類庫(如rt.jar,所有的java.*開頭的類均被Bootstrap ClassLoader載入)。啟動類載入器是無法被Java程式直接引用的。
擴展類載入器: Extension ClassLoader,該載入器由sun.misc.Launcher$ExtClassLoader
實現,它負責載入JDK\jre\lib\ext目錄中,或者由java.ext.dirs系統變數指定的路徑中的所有類庫(如javax.*開頭的類),開發者可以直接使用擴展類載入器。
應用程式類載入器
: Application ClassLoader,該類載入器由sun.misc.Launcher$AppClassLoader
來實現,它負責載入用戶類路徑(ClassPath)所指定的類,開發者可以直接使用該類載入器,如果應用程式中沒有自定義過自己的類載入器,一般情況下這個就是程式中預設的類載入器。
類的載入:
-
命令行啟動應用時候由JVM初始化載入
-
通過Class.forName()方法動態載入
-
通過ClassLoader.loadClass()方法動態載入
Class.forName()和ClassLoader.loadClass()區別?
-
Class.forName(): 將類的.class文件載入到jvm中之外,還會對類進行解釋,執行類中的static塊;
-
ClassLoader.loadClass(): 只乾一件事情,就是將.class文件載入到jvm中,不會執行static中的內容,只有在newInstance才會去執行static塊。
-
Class.forName(name, initialize, loader)帶參函數也可控制是否載入static塊。並且只有調用了newInstance()方法採用調用構造函數,創建類的對象 。
JVM類載入機制:
-
全盤負責
,當一個類載入器負責載入某個Class時,該Class所依賴的和引用的其他Class也將由該類載入器負責載入,除非顯示使用另外一個類載入器來載入 -
父類委托
,先讓父類載入器試圖載入該類,只有在父類載入器無法載入該類時才嘗試從自己的類路徑中載入該類 -
緩存機制
,緩存機制將會保證所有載入過的Class都會被緩存,當程式中需要使用某個Class時,類載入器先從緩存區尋找該Class,只有緩存區不存在,系統才會讀取該類對應的二進位數據,並將其轉換成Class對象,存入緩存區。這就是為什麼修改了Class後,必須重啟JVM,程式的修改才會生效 -
雙親委派機制
, 如果一個類載入器收到了類載入的請求,它首先不會自己去嘗試載入這個類,而是把請求委托給父載入器去完成,依次向上,因此,所有的類載入請求最終都應該被傳遞到頂層的啟動類載入器中,只有當父載入器在它的搜索範圍中沒有找到所需的類時,即無法完成該載入,子載入器才會嘗試自己去載入該類。
雙親委派機制過程?
-
當AppClassLoader載入一個class時,它首先不會自己去嘗試載入這個類,而是把類載入請求委派給父類載入器ExtClassLoader去完成。
-
當ExtClassLoader載入一個class時,它首先也不會自己去嘗試載入這個類,而是把類載入請求委派給BootStrapClassLoader去完成。
-
如果BootStrapClassLoader載入失敗(例如在$JAVA_HOME/jre/lib里未查找到該class),會使用ExtClassLoader來嘗試載入;
-
若ExtClassLoader也載入失敗,則會使用AppClassLoader來載入,如果AppClassLoader也載入失敗,則會報出異常ClassNotFoundException。
雙親委派優勢
-
系統類防止記憶體中出現多份同樣的位元組碼
-
保證Java程式安全穩定運行
-
JVM記憶體結構: