深入理解Java虛擬機器(一)
把"GC Roots"的物件作為起點,然後向下搜尋,搜尋所走過的路徑稱為引用鏈,當一個物件到GC Roots沒有任何引用鏈相連時,即該物件不可達,也就說明此物件是不可用的。
1、標記-清除演算法:標記出所有需要回收的物件,標記完統一清除被標記的物件。
缺點:
- 標記和清理的效率不高
- 標記清除後會產生大量不連續的記憶體碎片
2、複製演算法:將記憶體分為大小相等的兩塊,每次只用一塊,當這一塊記憶體用完,將存活物件複製到另一塊,將使用過的記憶體一次清理。
優缺點:
- 不會產生記憶體碎片的問題
- 缺點是將記憶體縮小到了之前的一半
- 在物件存活率高時進行多次複製操作,效率會低。
3、標記-整理演算法:標記需要回收的物件,將存活物件向一端移動(整理),清理掉可回收的物件。
4、分代收集演算法:根據物件存活週期不同,將Java堆記憶體分為新生代和老年代。
- 新生代:只有少量物件存活,使用複製演算法。
- 老年代:大量物件存活,使用標記清除或者標記整理演算法。
三、類載入機制
1、類載入時機
1、定義:
把Class檔案載入到記憶體中,並對資料進行校驗、解析和初始化,行成可被虛擬機器直接使用的Java型別。類從被載入到虛擬機器記憶體中開始,到卸載出記憶體結束。
2、生命週期:
- 載入
- 驗證
- 準備
- 解析
- 初始化
- 使用
- 解除安裝 載入、驗證、準備、初始化、解除安裝的順序確定。
3、需要對類進行初始化的場景
- new例項化物件、讀取或設定類的靜態欄位,呼叫類的靜態方法(被final修飾,已在編譯期將結果放入常量池的靜態欄位除外)
- 對類進行反射
- 初始化一個類,若父類還沒初始化,先觸發父類的初始化
- 需指定一個執行的主類(包含main方法的類),虛擬機器先初始化該類
- JDK1.7動態語言,MethodHandle例項解析結果REF_getStatis、REF_putStatis、REF_invokeStatis的方法控制代碼
4、不會方法初始化的場景:
所有引用類的方式不會觸發初始化,例如子類引用父類的靜態欄位,只觸發父類初始化。
5、介面初始化和類初始化的區別:
介面初始化時,不要求其父類介面全部完成初始化。
2、類載入過程
包括載入、驗證、準備、解析、初始化5步
1、載入:
- 通過類全限定名獲取定義該類的二進位制位元組流
- 將位元組流的靜態儲存結構轉換為方法區的執行時資料結構
- 記憶體中生成該類的Class物件,作為訪問該類資料的入口
2、驗證:
- 檔案格式驗證,驗證位元組流是否符合Class檔案格式規範
- 元資料驗證,對位元組碼描述的資訊進行語義分析
- 位元組碼驗證,確定語義合法
- 符號引用驗證,對常量池符號引用校驗
3、準備: 為類變數(static修飾的變數)分配記憶體並設定變數初始值
4、解析: 將常量池符號引用替換為直接引用(直接指向目標的指標)的過程
5、初始化: 開始執行類中定義的Java程式碼
3、類載入器
同一個Class檔案,被兩個不同的類載入器載入,這兩個類不相等。相等包括equals、instanceOf、isInstance方法返回的結果。
1、類別:
- 啟動類載入器(Bootstrap ClassLoader):載入<JAVA_HOME>\lib目標,或者被-Xbootclasspath引數指定的路徑,可被虛擬機器識別的類庫
- 擴充套件類載入器(Extension ClassLoader):載入<JAVA_HOME>\lib\ext目錄,或被java.ext.dirs系統變數指定的路徑的類庫
- 應用類載入器(Application ClassLoader):載入ClassPath上指定的類庫
2、雙親委託機制
除了頂層的類載入器外,其他的類載入器都有自己的父類載入器。父子之間通過組合來複用父載入器程式碼。
雙親委託機制的工作流程:一個類載入器收到類載入的請求,首先將請求委託給父類載入器去完成,最終所有載入請求都會傳遞給頂層的啟動載入器中。當父載入器發現未找到所需的類而無法完成載入請求時,子載入器才嘗試去載入。
ClassLoader
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { //檢查請求的類是否已經被載入 Class<?> c = findLoadedClass(name); if (c == null) { try { if (parent != null) { //讓父類載入器去嘗試載入 c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { //父類載入器拋異常 } if (c == null) { //然後呼叫自身的findClass方法來進行類載入 c = findClass(name); } } return c; } 複製程式碼
- 先檢查是否被載入過,如果沒有則呼叫父載入類去載入
- 父載入器為空,則呼叫啟動類載入器
- 父載入器載入失敗,則丟擲ClassNotFoundException異常
- 然後去呼叫自身的findClass方法去進行類載入