雙親委派
- 向上委託,向下載入
- 收到載入任務後,先交給父類載入器,只有當父類載入器無法完成,才會執行載入
- 保證只有一個類載入器載入,避免重複載入
- 破壞:JDK 1.2後才使用,JDK 1.1的核心類沒有通過雙親委派定義
如何判斷兩個Class物件是否相同
- class位元組碼相同
- classLoader相同
JVM執行資料區按執行緒使用情況分類
- 執行緒獨享區域
- 虛擬機器棧、本地方法棧、程式計數器
- 不需要垃圾回收
- 執行緒共享區域
- 堆和方法區
- 垃圾回收、類的靜態資料和物件
HotSpot
- JDK 1.6:字串常量池在永久代
- JDK 1.7:依然有永久代,但字串常量池在堆中
- JDK 1.8:不存在永久代
字串常量池
- 節省記憶體空間,所有類共享一個字串常量池
- 如何存資料
- 物件儲存
- 快速搜尋:StringTable(hashtable)
- StringTableSize:決定搜尋效率
堆
- JVM中最大的一塊記憶體
- GC最重要區域
- 堆的記憶體分配通過垃圾收集器實現
- JVM啟動時實現
- 存什麼
- JDK 1.6:物件、陣列
- JDK 1.7+:物件、陣列、字串常量池、靜態變數
- 引數
- -Xms:初始大小,單位m,g
- -Xmx:最大大小
- -XMn:新生代(年輕代)
- -XX:SurvivorRatio=8:年輕代中Eden與Servivor比例,預設為8:1
- -XX:PermSize:非堆記憶體初始大小,一般預設200m
- -XX:MaxPermSize:菲堆最大大小
- -XX:NewSize(-Xns):年輕代記憶體初始大小
- -XX:MaxNewSize(-Xmn):年輕代最大大小
- 分配原則
- 優先在Eden分配,如果Eden不足,進行一次MinorGC
- 大物件直接進入老年代(超過Eden一半)
- 長期存活物件進入老年代(15次GC未被回收)
- 年輕代沒有空間存放的物件進入老年代
- 分配方式
- 指標碰撞:記憶體地址連續(年輕代)
- 空閒列表:記憶體地址不連續(老年代)
- 分配安全
- 虛擬機器給A執行緒分配記憶體過程中,指標未修改,B執行緒可能同時使用了同一塊記憶體
- CAS(樂觀鎖):不加鎖,假設沒有衝突,如果衝突就重試,直到成功
- TLAB(本地執行緒分配緩衝):每個執行緒預先分配一塊記憶體
- 分配擔保
- 當在新生代中無法分配記憶體時,把新生代物件轉移到老年代,然後把新物件放入騰空的新生代
- 物件記憶體佈局
- 物件頭:鎖資訊、物件年齡、型別指標
- 例項資料:成員變數
- 對齊填充
程式計數器
- 當前執行緒執行下一個位元組碼的行號
- 用於執行緒切換後,能恢復到正確執行的位置
Java虛擬機器棧
- 基於棧和基於暫存器
- 基於棧(運算元棧):一個程式呼叫需要10個基於棧的指令,JVM一次編譯,到處執行
- 基於暫存器:同樣的一個程式呼叫只需要兩三個基於暫存器的呼叫,和作業系統有關
- 棧幀
- 支援虛擬機器進行方法執行的資料結構
- 儲存方法的區域性變量表、運算元棧、動態連結、方法返回地址
- 區域性變量表--冰箱;操作棧--盤子
- 方法呼叫時建立
- 運算元棧的每個元素可以是任意Java資料型別,32位資料型別佔一個棧容量
- 每個棧幀都包含一個指向執行時常量池中該棧所屬方法的符號引用,可在類載入階段或第一次使用時轉換為直接引用(靜態解析),或每次執行時轉換為直接引用(動態連線)
- 靜態解析:直接引用,即對應方法的記憶體地址
- 動態連結:class檔案中,一個方法呼叫其他方法,需要將方法的符號引用轉化為記憶體地址的直接引用。類中呼叫父類方法,執行時執行子類方法(多型)
- 方法返回:把當前棧幀出棧。正常完成出口 / 異常完成出口
本地方法棧
- 直接操作硬體,C++方法
方法執行
- 先由Java編譯器編譯為Java位元組碼,再由Java直譯器逐條解釋,或對熱點程式碼由JIT編譯器編譯執行
- 熱點程式碼:被多次呼叫的方法;被多次執行的迴圈體
- 方法呼叫計數器:先檢查是否存在已編譯版本,否則方法呼叫次數加1,超過閾值則啟動編譯
- 載入儲存指令
- 將一個區域性變量表載入到運算元棧:load系列
- 將一個數值從運算元棧載入到區域性變量表:store系列
- 將一個常量載入到運算元棧:const系列、push系列、ldc系列
- JIT執行方式
- Server模式,Client模式
- 優化
- 公共子表示式消除
- 方法內聯
- 逃逸分析(存在逃逸則無法優化)
- 棧上記憶體分配
- 標量替換
- 同步鎖消除
方法呼叫
- 常見方法呼叫型別
- 私有方法:與類繫結,編譯時確定
- 構造方法:與類繫結,編譯時確定
- 靜態方法:與類繫結,編譯時確定
- 成員方法:不與類繫結,執行時確定
- 介面方法:不與類繫結,執行時確定
- 編譯看左邊,執行看右邊
- 過載與重寫
- 重寫(overwrite):也叫方法覆蓋,繼承或實現關係下,子類和父類方法描述符(引數和返回值)一致,方法名稱一致
- 過載(overloading):在同一個類中,方法名稱一致,方法引數(型別和順序)不一致,不關心返回值
- 可理解為執行時重新載入,即編譯時按左邊類,執行時才載入右邊真正型別
- 靜態繫結和動態繫結
- 通過類名、方法名、方法描述符識別方法
- 屬性看左,方法看右
- 通過父類引用訪問子類的屬性,需要強制轉型
- 方法呼叫指令
- invokevirtual:呼叫非靜態非私有方法(多型)
- invokeinterface:呼叫介面方法(多型)
- invokespecial:呼叫非靜態私有方法、構造方法
- invokestatic:呼叫靜態方法
- invokedynamic
- 方法呼叫過程
- 靜態繫結方法:直接在執行時常量池找到引用
- 動態繫結方法:根據父類方法表確定要查詢的方法索引(編譯看左),從子類方法表中開始,找不到再去父方法表找
- 虛分配
- Father father = new Son();
- father指標指向son物件
- 根據本地變量表,找呼叫該虛方法的物件
- 取出物件頭中的型別指標,找到Class物件
- 找到Class物件後,找到對應的虛方法表
- 找到對應的方法,進行方法呼叫
- 如果找不到,則去父類物件查詢,最終找不到則報錯
public class DynamicCall01 {
public static void main(String[] args) {
Father father = new Son();
// 多型,發生方法過載
father.f1();
// 列印結果: Son-f1()
char c = 'a';
father.f1(c);
// 列印結果: father-f1() para-int 97
}
} // 被呼叫的父類
class Father {
public void f1() {
System.out.println("father-f1()");
} public void f1(int i) {
System.out.println("father-f1() para-int " + i);
}
} // 被呼叫的子類
class Son extends Father {
public void f1() {
// 覆蓋父類的方法
System.out.println("Son-f1()");
}
public void f1(char c) {
System.out.println("Son-s1() para-char " + c);
}
}
- 例題
- 對於father.f1(),子類父類都有,在各自方法表中是獨立的兩項(編號相同),編譯期間無法確定,故在執行時根據建立的物件,發生過載
- 對於father.f1(c),f(char)父類沒有,編譯時將char自動轉型為int,而f(int)只有父類有,子類直接繼承,也不會寫在子類方法表中,故不會發生過載。如果Son中也有f1(int i),則會發生過載
- 子類物件通過父類引用呼叫的方法,必須在父類中出現過,否則編譯無法通過
- 例題
- 內聯快取
- 加快動態繫結的優化技術
垃圾回收
- 標記階段
- 引用計數:實現簡單,無法處理迴圈引用問題。Python採用
- 可達性分析:列舉GC Roots-->STW-->追蹤標記。Java採用
- 回收過程
- 第一次判斷:使用可達性分析後,判斷物件不可達
- 第二次判斷:finalize(),重新建立可達性關聯,否則會被第二次標記
- 垃圾清除階段
- 標記-清除演算法:效率不高,GC時需停止程式,清理後記憶體不連續
- 複製演算法:沒有標記和清除過程,效率高,空間連續,需要兩倍空間(空間換時間),適用於新生代(存活物件少,垃圾物件多)
- 標記-壓縮演算法:適用於老年代(存活物件多)
- 垃圾回收器
- 按執行方式分類
- 序列回收器:Serial、Serial Old
- 並行回收器:ParNew、Parallel Scavenge、Parallel Old
- 併發回收器:CMS、G1
- 按垃圾分代關係
- 新生代收集器:Serial、ParNew、Parallel Scavenge
- 老年代收集器:Serial Old、Parallel Old、CMS
- 整堆收集器:G1
- 效能指標
- 吞吐量:執行使用者程式碼時間/(執行使用者程式碼時間+垃圾收集時間)【高吞吐,後臺】
- 暫停時間:應用程式執行緒暫停,GC執行緒執行的時間段【低延遲,頁面】
- CMS
- 低延遲
- 標記-清除演算法
- 不能和PS配合
- 初始標記->併發標記->重新標記->併發清除
- 按執行方式分類