JVM效能調優與實戰進階篇-上

itxiaoshen發表於2022-02-28

ZGC

誕生原因

Java生態非常強大,但還不夠,有些場景仍處於劣勢,而ZGC的出現可以讓Java語言搶佔其他語言的某些特定領域市場。比如

  • 谷歌主導的Android手機系統顯示卡頓。
  • 證券交易市場,實時性要求非常高,目前主要是C++主導。
  • 大資料叢集如HBase的效能。

特性

  • ZGC(The Z Garbage Collector)為JDK11推出一款低延遲的垃圾回收器。STW即停頓時間低於1ms,且不會隨著堆的大小增加而增加。

    • 實現主要原理:全併發處理(僅對GC ROOTS進行遍歷時會暫停)
  • 高版本JDK16之後支援16TB級別的堆;

    • 實現主要原理:Region分割槽管理、染色指標定址
  • 應用程式吞吐量最多減少15%。

    • 實現主要原理:當生命週期很短的物件分配速率很高的時候,大量物件不會被進行標記收集,會產生大量浮動垃圾從而影響吞吐量,並且堆中可轉移物件的空間就會越來越小。
  • 為未來的GC新特性奠定基礎。

    • 實現主要原理:染色指標中未被使用預留的18 bits。

記憶體佈局

ZGC採用堆空間分頁模型的機制,堆空間分頁模型也非常符合Linux Kernel2.6引入的標準大頁(huge page)如4KB的處理方式。本質與G1一樣,沒有分代的概念,ZGC也採用基於Region的堆記憶體佈局,不一樣的是ZGC的Region具有動態性:動態建立銷燬、動態容量大小。ZGC一共分為三種Region:

  • 小型Region(小頁面):容量固定為 2MB,存放小於256KB的物件。
  • 中型Region(中頁面):容量固定為 32MB,存放大於256KB小於4MB的物件。
  • 大型Region(大頁面):容量為 2*N MB,可以動態變化,每個大Region中只會存放一個大物件,並且不會被重分配(即後文介紹的物件的複製),因為大物件的複製代價高昂。

image-20220227134728097

image-20220227123743039

指標著色技術(Color Pointers)

  • ZGC只支援64位的系統,也即是64位的指標。
  • ZGC在JDK11的ZGC來分析中低42位即2的42次方來表示使用中的堆空間,也即是可管理的記憶體,而在JDK更高版本有所變化。
  • ZGC藉助幾位高位來做GC相關的事情比如快速實現垃圾回收的併發標記、轉移和重定位等。
  • 預留用來給未來的GC新特性預留的擴充套件點

image-20220227124515793

一段C程式mapping.c看下ZGC的64位虛擬地址空間的指標著色技術展示

image-20220227125500950

編譯執行,三個地址一樣,也即是同一個實地址對映到3個虛地址。

image-20220227125701554

整體流程

概述

主要分為兩步

  • 標記階段(標記垃圾)
  • 轉移階段(物件複製或移動)

image-20220227130147942

image-20220227133154167

垃圾標記

垃圾標記演算法採用可達性分析演算法

image-20220227130457115

  • Remapped
    • GC前所有記憶體都是Remapped,或者標記後如果還是Remapped則是垃圾。
  • M0,發生兩次GC為例,M0是1次GC。
    • 前一次GC的標記階段被標記過的活躍物件,但是上次GC未對物件進行轉移。
  • M1,發生兩次GC為例,M0是2次GC。
    • 本次垃圾回收中識別的活躍物件。

標記階段,物件分配(Remapped)

  • 初始標記(標記根)
  • 併發標記(標記剩餘)
  • 再標記(解決漏標)

標記結束後Remapped物件即為垃圾物件。而下次標記使用M1表示活躍。

image-20220227132624080

ZGC轉移

  • 如果是同一個頁面則等同於標記整理。
  • 如果是不同頁面等同於複製演算法。

JVM調優概述

背景

  • 生產環境中的問題

    • 生產環境中的問題。
    • 生產環境發生了記憶體溢位該如何處理?
    • 生產環境應該給伺服器分配多少記憶體合適?
    • 如何對垃圾回收器的效能進行調優?
    • 生產環境 CPU 負載飆高該如何處理?
    • 生產環境應該給應用分配多少執行緒合適?
    • 不加 log,如何確定請求是否執行了某一行程式碼?
    • 不加 log,如何實時檢視某個方法的入參與返回值?
  • 為什麼要調優

    • 防止出現 OOM
    • 解決 OOM
    • 減少 Full GC 出現的頻率
  • 調優場景

    • Full GC 次數頻繁。
    • GC 停頓時間過長(超過1秒)。
    • 應用出現OutOfMemory 等記憶體異常。
    • 系統吞吐量與響應效能不高或下降
  • 不同階段的考慮

    • 上線前
    • 專案執行階段
    • 線上出現 OOM

調優概述

  • 監控的依據
    • 執行日誌
    • 異常堆疊
    • GC 日誌
    • 執行緒快照
    • 堆轉儲快照
  • 調優的大方向
    • 合理地編寫程式碼
    • 充分併合理的使用硬體資源
    • 合理地進行 JVM 調優

調優目標

JVM調優目標是使用較小的記憶體佔用來獲得較高的吞吐量或者較低的延遲,從這裡也可以知道其重要指標有三個:

  • 記憶體佔用:程式正常執行需要的記憶體大小。
  • 延遲:由於垃圾收集而引起的程式停頓時間。
  • 吞吐量:使用者程式執行時間佔使用者程式和垃圾收集佔用總時間的比值。

從上面我們也知道這三者如同分散式CAP理論一樣不可完全兼得,對於一個Java程式同時保證記憶體佔用小、延遲低、高吞吐量是不可能的;任何一個指標效能的提高,幾乎都是以犧牲其他指標效能的損為代價的,不可兼得。程式的目標不同,調優時所考慮的方向也不同,因此需要結合實際場景,有明確的優化目標,找到效能瓶頸,對瓶頸有針對性的優化。

調優原則

  • 90%也即是大多數的Java應用不需要進行JVM優化。
  • 大多數導致GC問題的原因是程式碼層面的問題導致的(程式碼層面)。
  • 上線之前,應先考慮將機器的JVM引數設定到最優。
  • 減少建立物件的數量,減少使用全域性變數和大物件(程式碼層面)。
  • 優先架構調優和程式碼調優,JVM優化是不得已的手段。
  • 分析GC情況優化程式碼比優化JVM引數更好。

調優步驟

  • 第 1 步:效能監控
    • GC 頻繁
    • cpu load 過高(如top -hP 程式號;top -d 2 -c等)
    • OOM
    • 記憶體洩露
    • 死鎖
    • 程式響應時間較長
  • 第 2 步:效能分析
    • 列印 GC 日誌,通過 GCviewer 或者 gceasy來分析異常資訊
    • 靈活運用命令列工具、jstack、jmap、jinfo 等
    • dump 出堆檔案,使用記憶體分析工具分析檔案
    • 使用阿里 Arthas、jconsole、JVisualVM 來實時檢視 JVM 狀態
    • jstack 檢視堆疊資訊
  • 第 3 步:效能調優
    • 適當增加記憶體,根據業務背景選擇垃圾回收器
    • 優化程式碼,控制記憶體使用
    • 增加機器,分散節點壓力
    • 合理設定執行緒池執行緒數量
    • 使用中介軟體提高程式效率,比如快取、訊息佇列等

效能評價/測試指標

  • 停頓時間(或響應時間)
    • 提交請求和返回該請求的響應之間使用的時間,一般比較關注平均響應時間。
      • 資料庫查詢一條記錄(有索引),十幾毫秒。
      • 機械磁碟一次定址定位。4毫秒
      • 從機械磁碟順序讀取 1M 資料。2毫秒
      • 從 SSD 磁碟順序讀取 1M 資料。0.3毫秒
      • 從記憶體讀取 1M 資料。十幾微妙
      • Java程式本地方法呼叫。幾微妙
      • 網路傳輸2Kb資料。1微妙
  • 吞吐量
    • 對單位時間內完成的工作量(請求)的量度
    • 在 GC 中:執行使用者程式碼的事件佔總執行時間的比例(總執行時間:程式的執行時間+記憶體回收的時間)
    • 吞吐量為 1-1/(1+n),其中-XX::GCTimeRatio=n
  • 記憶體佔用
    • Java 堆區所佔的記憶體大小
  • 相互間的關係
    • 以高速公路通行狀況為例
    • 吞吐量:每天通過高速公路收費站的車輛的資料
    • 併發數:高速公路上正在行駛的車輛的數目
    • 響應時間:車速

JVM監控及診斷命令列工具

無監控、不調優!命令列安裝 jdk 的 bin 目錄,這些工具用來獲取目標 JVM 不同方面、不同層次的資訊,幫助開發人員很好地解決 Java 應用程式的一些疑難雜症。

  • 檢視正在執行的Java程式:jps
    • jps(Java Process Status):顯示指定系統內所有的 HotSpot 虛擬機器程式(檢視虛擬機器程式資訊),可用於查詢正在執行的虛擬機器程式。
    • 對於本地虛擬機器程式來說,程式的本地虛擬機器 ID 與作業系統的程式 ID 是一致的,是唯一的。
    • 基本使用語法為:jps [options] [hostid]
  • 檢視JVM統計資訊:jstat
    • jstat(JVM Statistics Monitoring Tool):用於監視虛擬機器各種執行狀態資訊的命令列工具。它可以顯示本地或者遠端虛擬機器程式中的類裝載、記憶體、垃圾收集、JIT 編譯等執行資料。在沒有 GUI 圖形介面,只提供了純文字控制檯環境的伺服器上,它將是執行期定位虛擬機器效能問題的首選工具。常用於檢測垃圾回收問題以及記憶體洩漏問題。(一般生產環境沒gui工具,簡單可常用這個)
    • 基本使用語法為:jstat - [-t] [-h] [ []],比如jstat -gc 程式id 1000 10
    • jstat 還可以用來判斷是否出現記憶體洩漏
      • 在長時間執行的 Java 程式中,我們可以執行 jstat 命令連續獲取多行效能資料,並取這幾行資料中 OU 列(即已佔用的老年代記憶體)的最小值。
      • 然後,我們每隔一段較長的時間重複一次上述操作,來獲得多組 OU 最小值。如果這些值呈上漲趨勢,則說明該 Java 程式的老年代記憶體已使用量在不斷上漲,這意味著無法回收的物件在不斷增加,因此很有可能存在記憶體洩漏。
  • 實時檢視和修改JVM配置引數:jinfo
    • jinfo(Configuration Info for Java):檢視虛擬機器配置引數資訊,也可用於調整虛擬機器的配置引數
    • 基本使用語法為:jinfo [options] pid,比如jinfo -sysprops 程式id
  • 匯出記憶體映像檔案&記憶體使用情況:jmap
    • 獲取 dump 檔案(堆轉儲快照檔案,二進位制檔案),它還可以獲取目標 Java 程式的記憶體相關資訊,包括 Java 堆各區域的使用情況、堆中物件的統計資訊、類載入資訊等。
    • 基本使用語法為:
      • jmap [option]
      • jmap [option] <executable
      • jmap [option] [server_id@]
    • 使用1:匯出記憶體映像檔案
      • 手動的方式
      • jmap -dump:format=b,file=<filename.hprof>
      • jmap -dump:live,format=b,file=<filename.hprof>
    • 使用2:顯示堆記憶體相關資訊
      • jmap -heap 程式id
      • jmap -histo 程式id
    • 使用3:其他作用
      • jmap -permstat 程式id
      • 檢視系統的ClassLoader資訊
      • jmap -finalizerinfo
      • 檢視堆積在finalizer佇列中的物件
  • JDK 自帶堆分析工具:jhat
    • jhat(JVM Heap Analysis Tool):Sun JDK 提供的 jhat 命令與 jmap 命令搭配使用,用於分析 jmap 生成的 heap dump 檔案(堆轉儲快照)。jhat 內建了一個微型的 HTTP/HTML 伺服器,生成 dump 檔案的分析結果後,使用者可以在瀏覽器中檢視分析結果(分析虛擬機器轉儲快照資訊)。
    • 使用了 jhat 命令,就啟動了一個 http 服務,埠是 7000,即 http://localhost:7000/,就可以在瀏覽器裡分析。
    • 說明:jhat 命令在 JDK9、JDK10 中已經被刪除,官方建議用 VisualVM 代替。
    • 基本適用語法:jhat
  • 列印JVM中執行緒快照:jstack
    • jstack(JVM Stack Trace):用於生成虛擬機器指定程式當前時刻的執行緒快照(虛擬機器堆疊跟蹤)。執行緒快照就是當前虛擬機器內指定程式的每一條執行緒正在執行的方法堆疊的集合。
    • 生成執行緒快照的作用:可用於定位執行緒出現長時間停頓的原因,如執行緒間死鎖、死迴圈、請求外部資源導致的長時間等待等問題。這些都是導致執行緒長時間停頓的常見原因。當執行緒出現停頓時,就可以用 jstack 顯示各個執行緒呼叫的堆疊情況。
    • 基本語法 : jstack [option] pid
  • 多功能命令列:jcmd
    • 在 JDK 1.7 以後,新增了一個命令列工具 jcmd。它是一個多功能的工具,可以用來實現前面除了 jstat 之外所有命令的功能。比如:用它來匯出堆、記憶體使用、檢視 Java 程式、匯出執行緒資訊、執行 GC、JVM 執行時間等。
    • jcmd -l:列出所有的 JVM 程式
    • jcmd pid help:針對指定的程式,列出支援的所有具體命令
    • jcmd pid 具體命令:顯示指定程式的指令命令的資料
  • 遠端主機資訊收集:jstatd
    • 之前的指令只涉及到監控本機的 Java 應用程式,而在這些工具中,一些監控工具也支援對遠端計算機的監控(如 jps、jstat)。為了啟用遠端監控,則需要配合使用 jstatd 工具。命令 jstatd 是一個 RMI 服務端程式,它的作用相當於代理伺服器,建立本地計算機與遠端監控工具的通訊。jstatd 伺服器將本機的 Java 應用程式資訊傳遞到遠端計算機。

JVM監控及診斷工具GUI

前面我們學習Arthas也是一種JVM監控及診斷工具GUI,本篇先丟擲影子,後續在單獨針對

  • JDK自帶的工具
    • jconsole:JDK 自帶的視覺化監控工具。檢視 Java 應用程式的執行概況、監控堆資訊、永久區(或元空間)使用情況、類載入情況等。
      • 從 Java5 開始,在 JDK 中自帶的 java 監控和管理控制檯。用於對 JVM 中記憶體、執行緒和類等的監控,是一個基於 JMX(java management extensions)的 GUI 效能監控工具。
    • Visual VM:Visual VM 是一個工具,它提供了一個可視介面,用於檢視 Java 虛擬機器上執行的基於 Java 技術的應用程式的詳細資訊。
      • VisualVM是–個功能強大的多合一故障診斷和效能監控的視覺化工具。
      • 它整合了多個JDK命令列工具,使用VisualVM可用於顯示虛擬機器程式及程式的配置和環境資訊(jps ,jinfo),監視應用程式的CPU、GC、堆、方法區及執行緒的資訊(jstat、jstack)等,替JConsole。
      • 在JDK 6 Update 7以後,Visual VM便作為JDK的一 部分發布(VisualVM 在JDK/bin目錄下)。此外,Visual VM也可以作為獨立的軟體安裝。
    • JMC:Java Mission Control,內建 Java Flight Recorder。能夠以極低的效能開銷收集 Java 虛擬機器的效能資料。
  • 第三方工具
    • MAT:MAT(Memory Analyzer Tool)是基於 Eclipse 的記憶體分析工具,是一個快速、功能豐富的 Java heap 分析工具,它可以幫助我們查詢記憶體洩漏和減少記憶體消耗。
      • MAT 不是一個萬能工具,它並不能處理所有型別的堆儲存檔案。但是比較主流的廠家和格式,例如 Sun,HP,SAP 所採用的 HPROF 二進位制堆儲存檔案,以及 IBM 的 PHD 堆儲存檔案等都能被很好的解析。
      • 最吸引人的還是能夠快速為開發人員生成記憶體洩漏報表,方便定位問題和分析問題。雖然 MAT 有如此強大的功能,但是記憶體分析也沒有簡單到一鍵完成的程度,很多記憶體問題還是需要我們從 MAT 展現給我們的資訊當中通過經驗和直覺來判斷才能發現。
    • JProfiler:商業軟體,需要付費。功能強大。
    • Flame Graphs(火焰圖),在追求極致效能的場景下,瞭解你的程式執行過程中 cpu 在幹什麼很重要,火焰圖就是一種非常直觀的展示 CPU 在程式整個生命週期過程中時間分配的工具和呼叫找中的 CPU 消耗瓶頸。

此外針對JVM執行時引數和分析GC日誌再單獨增加專題文件

**本人部落格網站 **IT小神 www.itxiaoshen.com

相關文章