雙親委派

  • 向上委託,向下載入
  • 收到載入任務後,先交給父類載入器,只有當父類載入器無法完成,才會執行載入
  • 保證只有一個類載入器載入,避免重複載入
  • 破壞: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配合
      • 初始標記->併發標記->重新標記->併發清除