1. 程式人生 > 程式設計 >【JVM 知識體系框架總結】

【JVM 知識體系框架總結】

JVM 記憶體分佈

  • 執行緒共享資料區:
    方法區->類資訊,靜態變數
    堆->陣列物件
  • 執行緒隔離區
    虛擬機器器棧-> 方法
    本地方法棧->本地方法庫 native
  • 堆、程式計數器
  • JVM 執行資料

程式計數器

執行緒隔離 ,比較小的記憶體空間,當前執行緒所執行的位元組碼的行號執行緒是一個獨立的執行單元,由 CPU執行唯一沒有 OOM 的地方,由虛擬機器器維護,所以不會出現 OOM

虛擬機器器棧

執行的是Java方法

方法的呼叫就是棧幀入虛擬機器器棧的過程棧幀:區域性變量表(變數) 、運算元棧(存放a+b的結果 )、 動態連結(對物件引用的地址),方法出口(return的值)執行緒請求的棧深度大於虛擬機器器所允許的深度StackOverflowError

本地方法棧

執行的是 native 方法的一塊 java記憶體區域,一樣有 棧幀hotspot將 Java 虛擬機器器棧和本地方法棧合二為一jvm標準是 java 虛擬機器器棧和本地方法棧分開

java記憶體中存放物件例項的區域,幾乎所有的物件例項都在這裡分配所有執行緒共享新生代、老年代jmap -heap pid;

方法區

各個執行緒共享的記憶體區域儲存已被虛擬機器器載入的類的資訊、常量、靜態變數、即時編譯器編譯後的程式碼等資料Hotspot用永久代實現方法區(讓垃圾回收器可以管理方法區),對常量池的回收和解除安裝方法區會丟擲 OOM,當他無法滿足記憶體分配需求時

執行時常量池

執行時常量池是方法區的一部分,Class 中除了欄位、方法、介面的 常量池,存放編譯器生成的字面量和符號引用,這部分內容由類載入後進入方法區的執行時常量池中存放。

StringTable是HashSet結構方法區的一部分,受到方法區的限制,依然會 OOM

Java 物件建立過程

-> static方法 static程式碼塊

  1. new 指令,判斷在常量池中有沒符號引用,有則已被載入過
  2. 判斷類是否被載入、解析、初始化
  3. 為新生物件在java堆裡分配記憶體空間
    1) 指標碰撞(記憶體比較整齊)

    步驟:1. 分配記憶體 2. 移動指標,非原子步驟可能出現併發問題,Java虛擬機器器採用 CAS 配上失敗重試的方式保證更新操作的原子性
    2)空閒列表(記憶體比較亂)
    儲存堆記憶體空閒地址
    步驟:1.分配記憶體 2. 修改空閒列表地址 非原子步驟可能出現併發問題,Java虛擬機器器採用 CAS 配上失敗重試的方式保證更新操作的原子性
  4. 將分配到記憶體空間都初始化零值
  5. 設定物件頭相關資訊 (GC分代年齡、物件的HashCode、元資料資訊)
  6. 執行方法

Java 物件記憶體佈局

物件屬性的值->例項資料物件頭 64 位機器存 64 位,32 位機器存 32 位,8 的倍數

Java 物件的訪問

  1. 直接指標訪問
  2. 控制程式碼訪問

    對比:
  3. 訪問效率:直接指標訪問效率高(hotspot採用這種方式)
  4. 垃圾回收:控制程式碼訪問效率高,垃圾回收只用更新控制程式碼池,而直接指標訪問方式則要更新 reference地址

垃圾回收演演算法

  1. 引用計數器

    當物件例項分配給一個變數時,該變數計數設定為 1,當任何其他變數被賦值為這個物件的引用的時,計數+1 (a =b,則b的引用物件例項計數器+1),當一個物件例項的某個引用超過了生命週期(方法執行完)或者被設定為一個新值,則該物件的例項引用計數器 -1
    無法解決迴圈引用
    可達性分析
    GC Root (虛擬機器器棧中的引用的物件、本地方法棧中引用的物件、方法區靜態屬性引用的物件、方法區常量引用的物件)
  2. 標記-清除

    標記需要回收的物件,在標記完成後統一回收
    不足:
    1.效率問題,標記清除 2 個過程效率都不高
    2.空間問題,標記清除後產生大量不連續的記憶體碎片,碎片過多當程式需要分配較大的物件時,無法找到足夠的連續記憶體而不得不提前觸發一次垃圾回收動作
  3. 標記-複製

    記憶體塊 A存活的物件複製到記憶體塊 B (Survivor to)裡,然後將記憶體塊A (Eden + Survivor from)清空,
    只有少部分物件移動,更多的物件是要被回收的
    Eden:Survivor from:Survivor to=8:1:1
    98%物件“朝生夕亡”,新生代可用記憶體容量 90%(80%+10%),98%的物件可回收是一般情況,當小於 90%的物件被回收的時候(10%以上的物件存活時),則 Survivor to 空間不夠,則需要依賴老年代進行分配擔保
  4. 標記-整理

    老年代不適合複製演演算法
  5. 複製操作增多 2. 額外 50%空間浪費 3. 經常需要額外的空間分配擔保 4.可能老年代中物件 100% 存活

步驟:

  1. 標記
  2. 整理 將存活的物件移動到一端(左上方),從不規整變成規整,然後直接清理掉邊界以外的記憶體

Serial 收集器

單執行緒垃圾回收器,使用者執行緒到安全點先暫定,然後 GC 執行緒單執行緒序列進行,等 GC 執行緒回收完,然後使用者執行緒再繼續特點:Stop the world
場景:桌面應用 (gc時間短)用於新生代,client 端

ParNew 收集器

Serial收集器的多執行緒版本用於新生代,唯一能和CMS 收集器配合工作,執行在 server 模式下-XX:ParallelGCThreads 限制垃圾收集器執行緒數 = CPU 核數(過多會導致上下文切換消耗)並行:多條垃圾收集執行緒並行工作,使用者執行緒仍然處於等待狀態併發:使用者執行緒與垃圾收集器同時執行,使用者執行緒和垃圾執行緒在不同 CPU 上執行

Parallel Scavenge 收集器

新生代收集器,複製演演算法,並行的多執行緒收集器關注吞吐量優先的收集器(吞吐量 = CPU 執行使用者程式碼執行時間/CPU 執行總時間 ,比如: 99%時間執行使用者執行緒,1%時間回收垃圾,這時吞吐量為 99%)高吞吐量可以高效率利用 CPU 時間,儘快完成程式的運算任務,適合在後臺運算而不需要太多的互動任務CMS 關注縮短垃圾回收停頓時間,適合與使用者互動的程式,良好的響應速度能提升使用者體驗-XX:MaxGCPauseMillis 引數 GC 停頓時間,引數過小會頻繁 GC-XX:GCTimeRatio 引數,預設 99%(使用者執行緒時間佔 CPU 總時間的 99%)

Serial Old 收集器

是Serial 收集器的老年代版本單執行緒老年代收集器,採用“標記-整理”演演算法

Parallel Old 收集器

是 Parallel Scavenge收集器的老年代版本多執行緒老年代收集器,採用“標記-整理”演演算法

CMS 收集器

獲取最短回收停頓時間為目標的收集器,採用“標記-清除”演演算法,用於網際網路、B/S 系統重視響應的系統

步驟:

  1. 初始標記(不和使用者執行緒一起執行,耗時短)—— 標記一下 GC Roots 能直接關聯到的物件,速度很快
  2. 併發標記(和使用者執行緒一起執行,耗時長) —— 併發標記階段就是進行 GC RootsTracing,尋找 GC 引用鏈
  3. 重新標記(不和使用者執行緒一起執行,耗時短)—— 為了修正併發標記期間因使用者執行緒導致標記產生變動的標記記錄
  4. 併發清除(和使用者執行緒一起執行,耗時長)—— 掃描整個記憶體區域

缺點 :

  1. 對 CPU 資源非常敏感(併發標記階段時間長,佔用使用者執行緒 CPU 時間)
  2. 無法處理浮動垃圾(程式在進行併發清除時,使用者執行緒所產生的新垃圾)
  3. 標記-清除產生空間碎片

G1 收集器

面向服務端應用的垃圾收集器Region->Remembered Set (解決 迴圈引用 )
檢查 Reference (程式對reference型別寫操作,檢查 reference 引用型別)步驟:

  1. 初始標記 —— 標記 GC Roots 能直接關聯到的物件
  2. 併發標記 —— 從 GC Root 開始對堆中物件進行可達性分析,找出存活物件 ,這一階段耗時較長,但可與使用者程式併發執行
  3. 最終標記(Remembered Set Logs->Remembered Set)—— 修正在併發標記期間因使用者程式繼續運作而導致標記產生變動的那一部分標記記錄,虛擬機器器將這段時間物件變化記錄線上程 Remembered Set Logs裡面,最終標記階段需要把 Remembered Set Logs的資料合併到 Remembered Set中
  4. 篩選回收(Live Data Counting and Evacuation)—— 只需要掃描 Remembered Set
    優勢:
  5. 基於“標記-整理” 為主和 Region 之間採用複製演演算法實現
  6. 可預測停頓,降低停頓時間,但G1 除了追求低停頓外,還能建立可預測的停頓時間模型
  7. G1 直接對 Java 堆中的 Region 進行回收(新生代、老年代不再物理隔離,他們都是一部分 Region)
  8. 可預測的停頓時間模型,G1 跟蹤各個 Regions 裡面的垃圾堆積的價值大小(回收所獲得的空間大小以及回收所需時間的經驗值),在後臺維護一個優先列表,每次根據允許的收集時間,優先回收價值最大的 Region

堆記憶體分配

Java 堆分佈圖

物件分配的規則:

  1. 物件主要分配在新生代的 Eden 區 ( Eden區,From 區存活物件複製到 To區,Eden區,From區被回收,然後 To區物件拷貝到From區,再進行下一次垃圾回收 )
  2. 如果啟動了本地執行緒分配緩衝,將按執行緒優先在 TLAB 上分配
  3. 少數情況下也可能直接分配到老年代 (放不下From和To區的都直接放到老年代)

大物件分配

大物件是指需要大量連續記憶體空間的 Java 物件,最典型的大物件是是那種很長的字串以及陣列-XX:PretenureSizeThreshold 設定大於該值的物件直接分配在老年代,避免在 Eden 區以及 2 個Survivior區之間發生大量的記憶體複製

逃逸分析和棧上分配

逃逸分析:分析物件動態作用域,當一個物件在方法中被定義後,它可能被外部方法所引用,稱為方法逃逸。甚至還有可能被外部執行緒訪問到,比如賦值給類變數或其他執行緒中訪問的例項變數,稱為執行緒逃逸。棧上分配:把方法中的變數和物件直接分配到棧上,方法執行完後自動銷燬,不需要垃圾回收介入,從而提高系統效能-XX:+DoEscapeAnalysis 開啟逃逸分析(jdk1.8預設開啟 )-XX:-DoEscapeAnalysis 關閉逃逸分析

命令

  1. ps -ef | grep java
  2. jps -m(啟動引數) -l(類名) -v (JVM 引數)
  3. jstat -gc 27660 250 20 監視虛擬機器器各種執行狀態資訊
  4. jinfo 27660 檢視和調整程式虛擬機器器(未被顯示指定的)引數資訊
  5. jmap 生成堆轉儲快照 -XX:+HeapDumpOnOutOfMemoryError
    jmap -heap 9366;
    jmap -histo 9366 | more; 顯示堆中物件統計
    jmap -dump:format=b,file=/Users/mousycoder/Desktop/a.bin 9366 生成dump檔案
    -Xmx20m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/Users/mousycoder/Desktop/
    jhat /Users/mousycoder/Desktop/java_pid9783.hprof 圖形分析Heap
    select s.toString() from java.lang.String s where (s.value != null && s.value.length > 1000 )
  6. jstack 執行緒快照(虛擬機器器內每一條執行緒正在執行的方法堆疊的集合,主要用於定位執行緒問題)
    shutdownHook 在關閉之前執行的任務
    jstack -l -F pid 強制輸出

執行緒狀態

  1. NEW
  2. RUNNABLE
  3. BLOCKED 一個正在阻塞等待一個監視器的執行緒處於這個狀態(Entry Set)被動的阻塞
  4. WAITING 一個正在無限期等待另一個執行緒執行一個特別的動作的執行緒處於這一狀態 (Wait Set)主動顯式申請的阻塞
  5. TIMED_WAITING 一個正在限時等待另一個執行緒執行一個動作的執行緒處於這一狀態
  6. TERMINATED 執行緒完成一個excution

JConsole

基於 JMX 的視覺化監視、管理工具開啟 JMX 埠nohup java -Xms800m -Xmx800m -Djava.rmi.server.hostname=192.168.1.250 -Dcom.sun.management.jmxremote.port=1111 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -jar hc-charging-server.jar &

網際網路開發流程Jconsole 記憶體分析思考過程

FullGC

Minor GC:當 Eden 區滿,觸發 Minor GCFullGC:

  1. 呼叫System.gc() 建議虛擬機器器進行 Full GC,可通過 -XX:+DisableExplicitGC 來進位制 RMI 呼叫System.gc()
  2. 老年代空間不足 大物件直接進入老年代,長期存活的物件進入老年代,當執行 Full GC後空間仍然不足,則丟擲 OutOfMemoryError,為了避免上面原因引起 Full GC,調優時儘量做到讓物件在 Minor GC 階段被回收,讓物件在新生代多存活一段時間以及不要建立過大的物件和陣列
  3. 空間分配擔保失敗 使用複製演演算法的 Minor GC 需要老年代的記憶體空間作為擔保,如果出現了 HandlePromotionFailure 擔保失敗,則會觸發 Full GC
    建議:
  4. 減少-Xmx大小,縮短 GC 時間(堆記憶體設定越大,Full GC 時間越長,停頓時間也會越長)
  5. 叢集部署

網際網路問題

  1. 白名單問題
    解決方法:list.contain->set.contain->布隆過濾器(使用者量大和使用者量小系統解決方案不一樣)
  2. 死鎖
    解決方法:jstack 以及 new thread帶上名稱
  3. 堆記憶體洩露
    FullGC 出現正常頻率為一天 1~2 次
    解決方案:jmap,heap dump on oom + jhat
  4. 堆外記憶體洩露
    heap堆使用率很低,但是有 OOM 以及 Full GC
    解決方法:btrace

學習祕籍

  1. 知識體系
  2. 面試前看下知識體系導圖
  3. 堅持就勝利