1、運行時數據區域
一、程序計數器:
- 當前線程執行字節碼的行號指示器(經過改變計數器的值來選擇下條須要執行的字節碼指令)
- 每一個線程有獨立的程序計數器(線程私有,爲了切換線程時能恢復到掙錢的執行位置)
- 若是執行java方法,計數器記錄正在執行的字節碼指令地址。若是執行的是Native方法,計數器爲空。
- 惟一沒規定任何OutOfMemoryError狀況的區域。
二、虛擬機棧
- 爲執行Java方法服務
- 線程私有,聲明週期跟線程一致
- 一個Java方法執行到結束的過程:棧幀從入棧到出棧的過程
- 棧幀存儲局部變量表(包括基本數據類型和對象的引用類型)、操做棧、動態連接、方法出口等信息
- 異常:線程請求的棧深度大於虛擬機容許的深度,拋出StackOverflowError。虛擬機動態擴展過程當中沒法申請到足夠的內存,會拋出OutOfMemoryError異常。
三、本地方法棧
- 爲虛擬機用到的Native方法服務
- 也會拋出StackOverflowError和OutOfMemoryError的異常
四、Java堆
- 用來存儲對象的實例
- 全部線程共享的一塊內存區域
- 從內存回收的角度能夠分爲新生代和老年代
五、方法區
- 存放被虛擬機加載的類信息、常量、靜態變量等
- 線程共享
六、運行時常量池
- 方法區的一部分
- 存放編譯期生成的各類字面量和符號引用
2、垃圾回收(GC)
一、判斷對象是否存活
一、引用計數法:java
- 給Java對象添加一個引用計數器,每當有一個地方引用它時,計數器+1;引用失效則-1。當計算器不爲0時,判斷對象存活
- 缺點:若是兩個對象相互循環引用時,由於計算器不爲0,不能被回收。實際上對象應該被回收。
二、可達性分析算法:算法
(1)原理:
把"GC Roots"的對象做爲起點,而後向下搜索,搜索所走過的路徑稱爲引用鏈,當一個對象到GC Roots沒有任何引用鏈相連時,即該對象不可達,也就說明此對象是不可用的。
(2)可做爲GC Root對象:bash
- 虛擬機棧(棧幀中的本地變量表)中引用的對象
- 方法區中類靜態屬性引用的對象
- 方法區常量引用的對象
- 本地方法棧中JNI引用的鍍錫
三、判斷一個類可回收:數據結構
- 該類全部實例對象被回收
- 加載該類的ClassLoader已經被回收
- 該類對應的Class對象沒被引用,沒法經過反射訪問該類的方法
二、引用類型
經過引用的強度分爲強、軟、弱、虛四種引用類型ui
- 強應用:通常Object b=new Object()這類引用,只要強引用存在,GC就不會回收被引用的對象。
- 軟引用:系統在發生內存溢出以前,會將這些對象二次回收。若是還沒足夠內存,纔會拋出內存溢出異常。經過SoftReference來實現。
- 弱引用:GC回收時,不管內存是否足夠,都會收會被弱引用關聯的對象。經過WeakReference來實現。
- 虛引用:做用是在對象被回收時收到一個系統通知。經過PhantomReference來實現。
三、垃圾收集算法
一、標記-清除算法:標記出全部須要回收的對象,標記完統一清除被標記的對象。spa
缺點:線程
- 標記和清理的效率不高
- 標記清除後會產生大量不連續的內存碎片
二、複製算法:將內存分爲大小相等的兩塊,每次只用一塊,當這一塊內存用完,將存活對象複製到另外一塊,將使用過的內存一次清理。指針
優缺點:code
- 不會產生內存碎片的問題
- 缺點是將內存縮小到了以前的一半
- 在對象存活率高時進行屢次複製操做,效率會低。
三、標記-整理算法:標記須要回收的對象,將存活對象向一端移動(整理),清理掉可回收的對象。cdn
四、分代收集算法:根據對象存活週期不一樣,將Java堆內存分爲新生代和老年代。
- 新生代:只有少許對象存活,使用複製算法。
- 老年代:大量對象存活,使用標記清除或者標記整理算法。
3、類加載機制
一、類加載時機
一、定義:
把Class文件加載到內存中,並對數據進行校驗、解析和初始化,行成可被虛擬機直接使用的Java類型。類從被加載到虛擬機內存中開始,到卸載出內存結束。
二、生命週期:
- 加載
- 驗證
- 準備
- 解析
- 初始化
- 使用
- 卸載 加載、驗證、準備、初始化、卸載的順序肯定。
三、須要對類進行初始化的場景
- new實例化對象、讀取或設置類的靜態字段,調用類的靜態方法(被final修飾,已在編譯期將結果放入常量池的靜態字段除外)
- 對類進行反射
- 初始化一個類,若父類還沒初始化,先觸發父類的初始化
- 需指定一個執行的主類(包含main方法的類),虛擬機先初始化該類
- JDK1.7動態語言,MethodHandle實例解析結果REF_getStatis、REF_putStatis、REF_invokeStatis的方法句柄
四、不會方法初始化的場景:
全部引用類的方式不會觸發初始化,例如子類引用父類的靜態字段,只觸發父類初始化。
五、接口初始化和類初始化的區別:
接口初始化時,不要求其父類接口所有完成初始化。
二、類加載過程
包括加載、驗證、準備、解析、初始化5步
一、加載:
- 經過類全限定名獲取定義該類的二進制字節流
- 將字節流的靜態存儲結構轉換爲方法區的運行時數據結構
- 內存中生成該類的Class對象,做爲訪問該類數據的入口
二、驗證:
- 文件格式驗證,驗證字節流是否符合Class文件格式規範
- 元數據驗證,對字節碼描述的信息進行語義分析
- 字節碼驗證,肯定語義合法
- 符號引用驗證,對常量池符號引用校驗
三、準備: 爲類變量(static修飾的變量)分配內存並設置變量初始值
四、解析: 將常量池符號引用替換爲直接引用(直接指向目標的指針)的過程
五、初始化: 開始執行類中定義的Java代碼
三、類加載器
同一個Class文件,被兩個不一樣的類加載器加載,這兩個類不相等。相等包括equals、instanceOf、isInstance方法返回的結果。
一、類別:
- 啓動類加載器(Bootstrap ClassLoader):加載<JAVA_HOME>\lib目標,或者被-Xbootclasspath參數指定的路徑,可被虛擬機識別的類庫
- 擴展類加載器(Extension ClassLoader):加載<JAVA_HOME>\lib\ext目錄,或被java.ext.dirs系統變量指定的路徑的類庫
- 應用類加載器(Application ClassLoader):加載ClassPath上指定的類庫
二、雙親委託機制
除了頂層的類加載器外,其餘的類加載器都有本身的父類加載器。父子之間經過組合來複用父加載器代碼。
雙親委託機制的工做流程:一個類加載器收到類加載的請求,首先將請求委託給父類加載器去完成,最終全部加載請求都會傳遞給頂層的啓動加載器中。當父加載器發現未找到所需的類而沒法完成加載請求時,子加載器才嘗試去加載。
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方法去進行類加載