Java效能優化全攻略
讓Java應用程式執行是一回事,但讓他們跑得快就是另外一回事了。在面對物件的環境中,效能問題就像來勢凶猛的野獸。但JVM的複雜性將效能調整的複雜程度增加了一個級別。這裡Refcard涵蓋了JVM internals、class loading(Java8中更新以對映最新的元空間)、垃圾回收、故障診斷、檢測、併發性,等等。
介紹
Java是目前軟體開發領域中使用最廣泛的程式語言之一。Java應用程式在許多垂直領域(銀行、電信、醫療保健等)中都有廣泛使用。Refcard的目的是,幫助開發者通過專注於JVM內部,效能調整原則和最佳實踐,以及利用現有監測和故障診斷工具,來提升應用程式在商業環境中的效能。
它能以不同的方式定義“optimal performance(最佳效能)”,但基本要素是:Java程式在業務響應時間要求內執行計算任務的能力,程式在高容量下執行業務功能的能力,並具有可靠性高和延遲低的特點。有時,數字本身變得模式化:對於一些大型網站,優秀的頁面響應時間應該在500ms以下。在適當的時候,Refcard包括目標數字。但在大多數情況下,您需要根據業務需求和現有的效能基準自己決定這些。
JVM Internals
基礎知識
程式碼編譯和JIT
編譯Java位元組碼顯然沒有直接從主機執行本機程式碼那麼快。為了提高效能,Hotspot JVM找出最繁忙的位元組碼區域,然後將其編譯成更高效地原生、機器程式碼(自適應優化)。然後這種原生代碼就會儲存在非堆記憶體中的程式碼快取中。
注意:多數的JVM是通過禁用JIT編譯器實現的(Djava.compiler=NONE)。您只需要考慮禁用的關鍵性優化,比如JVM崩潰。
下圖說明了Java原始碼,即時編譯流程和生命週期。
記憶體空間
HotSpot Java Virtual Machine是由以下的儲存空間組成。
儲存空間 | 描述 |
Java Heap | Java程式類例項和陣列的主儲存器。 |
Permanent Generation(JDK 1.7及以下版本) Metaspace (JDK 1.8及以上版本) |
Java類元資料的主儲存器。 注意:從Java 8開始,PermGen空間就由元空間和使用本地儲存器替換了,類似於IBM J9 JVM。 |
Native Heap(C-Heap) | 本地記憶體儲存執行緒、棧、包括物件的程式碼快取,如MMAP檔案和第三方本機庫。 |
類載入
Java的另一個重要特點是,在JVM啟動之後,它能夠載入編譯的Java類(位元組碼)。根據程式的大小,在剛剛重啟之後,程式在類載入過程中效能會顯著降低。這種現象是因為內部JIT編譯器在重啟之後需要重新開始優化。
自JDK 1.7版本之後,有一些改進值得大家重視。例如預設的JDK class loader具有更好的裝在類併發能力。
熱點
關注的區域 | 建議 |
JVM重啟後的效能下降 | 避免部署過量的Java類到一個單一的應用程式類載入器(例如:非常大的WAR檔案) |
執行時發現過多的類載入爭奪(thread lock, JAR file searches...) ,降低了整體效能。 |
分析您的應用程式並識別程式碼模組進行動態類載入操作過於頻繁。積極尋找非一站式類載入錯誤,如ClassNotFoundException和NoClassDefFoundError。 再訪Java對映API和適用情況下優化的過度使用。 |
java.lang.OutOfMemoryError: PermGen space (JDK 1.7及以下版本) java.lang.OutOfMemoryError:元空間(JDK 1.8及以上版本) |
再訪JVM Permanent Generation、Metaspace (MaxMetaSpaceSize)和本地記憶體容量在適用情況下的尺寸。 分析應用程式類載入器和識別元資料的記憶體洩漏的源頭。 |
故障診斷和監視
目標 | 建議 |
跟蹤那些載入到不同的類載入器的Java類。 | 配置程式選擇使用的Java profiler,例如JProfiler或Java VisualVM。將重點放在類載入器的操作和記憶體佔用上。可以通過–verbose:class. for the IBM JVM,生成多個Java核心快照跟蹤活動的類載入器和載入類。 |
調查類元資料的記憶體洩露的可以來源。 |
配置程式和定義可能的culprit(s)。生成並分析JVmheap dump快照,專注於類載入器和java.lang.Class中的例項。 |
確保適當的Permanent Generation / Metaspace和本地記憶體大小。 |
密切監視你的PermGen、元空間和本機記憶體利用率,並調整到適合的最大容量。 分析程式類載入器的大小,並尋找機會適當地減少元資料足跡。 |
垃圾回收
Java垃圾回收流程對於程式效能是至關重要的。為了提供有效的垃圾回收,Heap(堆)本質上是劃分在子區域中。
堆區域
區域 | 描述 |
最新一代-Young Generation (nursery space) |
新的或短暫的物件分配保留堆的一部分。 垃圾被一個fast but stop-the-world YG的收集器進行回收。 在young space中呆了足夠久的物件就會提升到old space。 注意:YG space的尺寸和GC頻率過高將會顯著影響程式的響應時間,從而導致JVM的暫停時間增加。 |
老一代-Old Generation (tenured space) |
heap的一部分留給了long-lived物件。 垃圾通常通過平行或併發(多數時候)進行收集,諸如CMS或gencon (IBM JVM)。 效能提示:根據應用程式的需求選擇並測試最佳的GC策略是非常重要的。例如,當切換到併發GC收集(如CMS或G1)可以顯著提高應用程式的平均響應時間(減少延遲)。 |
GC Collectors
選擇正確的collector或GC policy可以將程式的效能、可擴充套件性和可靠性優化到最佳狀態。許多應用程式對於響應時間延遲都很敏感,因此大多需要使用併發的回收器,例如HotSpot CMS或IBM GC policy balanced。
我們強烈建議您通過適當的效能和負載測試確定最合適的GC策略。應該在生產環境中執行全面監控策略,以跟蹤整體的JVM效能,並確定在之後需要改進的領域。
GC | 論據 | 描述 |
序列回收器 | -XX:+UseSerialGC (Oracle HotSpot) |
無論新舊回收器都使用單獨CPU,像是一種stop the world的時尚。 |
並行回收器 (吞吐量回收器) |
-XX:+UseParallelGC -XX:+UseParallelOldGC -Xgcpolicy:optthruput |
旨在利用CPU的核心優勢。無論新舊回收器都使用多個Gcthreads(via –XX:ParallelGCThreads=n),從而更好地利用來自主機的可用的CPU核心來完成。 注意:雖然回收時間可以顯著減少,但是有著大尺寸堆的程式面臨著large、stop-the-world、old回收,並且響應時間也受到影響。 |
確保適當的Permanent Generation / Metaspace和本地記憶體大小。 |
密切監視你的PermGen、元空間和本機記憶體利用率,並調整到適合的最大容量。 分析程式類載入器的大小,並尋找機會適當地減少元資料足跡。 |
旨在最大限度地減少舊一代stop-the-world回收器對程式響應時間的影響。 大多數使用CMS collector的老一代回收器與所述應用程式的執行同時進行。 注意:YoungGen collections仍然有stop-the-world事件,因此需要適當的微調,以減少總JVM暫停時間。 |
Garbage First (G1) Collector
HotSpot G1 collector是專為是專為滿足使用者定義的垃圾回收(GC)高概率暫停時間設計的,同時實現高吞吐量。
最新的HotSpot collector將heap基本劃分到一組大小相等的堆區域,虛擬記憶體的每個區域連續範圍。它將回收壓縮的活動集中在heap區域,那裡充滿了可回收的物件(garbage first)。換句話說就是,這個區域有最低限度的“live”物件。
Oracle建議在以下例子和情況下使用G1 collector,尤其是對於目前正在使用CMS或parallel collectors的:
- 專為large heaps(>= 6 GB),並限制GC延遲(暫停時間<= 0.5秒)的應用程式設計。
- 超過50%的Java heap被實時資料佔用(物件不能被GC回收)。
- 物件分析率和促進作用顯著變化。
- 不期望過長的垃圾回收或壓縮停頓(超過0.5至1秒)。
Java Heap尺寸
你一定要知道沒有GC策略可以挽救Java Heap尺寸不足的現象。這些演習涉及到為不同的儲存空間(包括新舊不同的版本)配置最大和最小的容量,包括元資料和本地記憶體容量。這裡有一些建議準則:
- 在32-bit或64-bit JVM之間進行明智的選擇。如果程式執行需要超過2GB記憶體,並且JVM暫停時間在可接受範圍內,可以考慮使用64-bit JVM。
- 永遠將應用程式放在第一考慮。確保將其配置好,並根據程式的記憶體佔用量調整heap尺寸。建議通過效能和負載測試來衡量實時資料佔有量。
- larger heap並不總是表現得更好、更快,因此不需要過度調整Java heap。並行中的JVM效能調優,找準機會減少或“spread”程式的記憶體佔有量,以保證JVM的平均響應時間<1%。
- 對於32-bit JVM,為了從元資料和本地heap中留出一些記憶體,考慮2GB的最大heap尺寸。
- 對於64-bit JVM,我們要想辦法在垂直和水平層面進行擴充套件,而不是試圖將Java heap尺寸增加到15GB以上。這種做法往往提供更好的吞吐量,更好地利用硬體,提高應用程式的故障切換功能。
- 不許重複開發:充分利用開源以及商業故障排除的優勢和監控工具,使這些變成可能。APM(應用效能管理)產品在過去十年裡發展迅猛。
JDK 1.8 Metaspace指南
目標 | 建議 |
記憶體大小 GC調整 監控和故障排除 |
預設情況下,元空間記憶體空間是無界的,並使用可用於動態擴充套件的process或OS native memory。記憶體空間分成快並通過mmap被JVM進行儲存。我們建議保持預設設定,以動態調整模式為出發點,將簡化的尺寸與密切監測的應用程式元資料佔有量相結合,從而進行更好的容量規劃。 新增一個JVM選項(-XX:MaxMetaspaceSize=<NNN>),可以讓您限制分配給class metadata的本地記憶體。當面臨物理資源(RAM)緊張或類似於記憶體洩露的情況時,建議將它作為一個保障機制。 對那種具有larger class metadata footprint或dynamic classloading的Java應用程式,我們建議通過新的JVM選項調整初始元空間大小 :-XX:MetaspaceSize=<NNN>,例如:1GB。這種調整方法將有助於避免包括class metadata在內的早期垃圾回收,尤其是在Java應用程式的 “warm-up”期。 |
Hot Spots
故障診斷和監視
目標 | 建議 |
測量和監視應用程式YoungGen和OldGen記憶體佔用,包括GC活動。 為您的應用程式決定正確的GC策略和Java堆大小。 調整應用程式的記憶體佔用量,如live物件。 |
分析、監控您所使用的Java分析工具,如JProfiler、Java VisualVM或其他商業APM產品。 允許通過–verbose:gc記錄JVM GC活動。您也可以使用類似GCMV(GC Memory Visualizer)的工具檢視JVM的暫停時間和記憶體分配率。 效能提示:過多的記憶體分配率可能意味著需要進行垂直和橫向擴充套件,或從多個JVM程序中分離出實時資料。 為了long-lived物件或long-term實時資料考慮,可以生成並分析JVM heap dump快照。Heap dump分析對於程式記憶體佔用(retention)的優化是非常有幫助的。 效能提示:由於從32位到64位,Java應用程式對heap 的需求會比原來高1.5倍。所以,在Java 1.7及以下的版本(這是預設的)中使用 -XX:+UseCompressedOops是非常重要的。這樣的引數調整大大減輕了64位JVM的效能壓力。 |
調查OutOfMemoryError 問題,尋找OldGen記憶體洩露的根源。 |
使用類似Java VisualVM、Plumbr的工具(Java記憶體洩漏檢測器),分析可能存在的內容洩露。 效能提示:要著重分析最大的Java物件上。要意識到降低記憶體佔有量就意味著提升效能,並降低GC活動。 使用類似 Memory Analyzer的工具生成並分析JVM heap dump快照。 |
Java併發性
Java併發性可以定義為程式同時執行多個任務的能力。對於大型的Java EE系統,這意味著執行多個使用者的業務功能的同時,實現最佳的吞吐量和效能的能力。
無論是硬體能力還是JVM穩定狀況,Java併發性問題可能引起程式的癱瘓,嚴重影響程式的整體效能和可用性。
Thread Lock Contention
當您評估Java應用程式的併發執行緒的穩定狀況時,你會經常遇到Thread lock contention的問題,這是目前最常見的Java併發問題。
例如:Thread lock contention會觸發non-stop,它會嘗試將一個缺少Java類(ClassNotFoundException的)載入到預設的JDK 1.7 ClassLoader。
如果您在成熟的技術環境中遇見像Thread Dump analysis這樣的問題,我們強烈建議您積極面對它。這個問題的根源通常不同於之前的Java synchronization to legitimate IO blocking或者其他的non-thread safe calls。Lock contention問題往往是另一個問題的“症狀”。
Java-level Deadlocks
真正的Java-level deadlocks是不太常見的,它同樣可以極大程度地影響應用程式的效能和穩定性。當遇到兩個或多個執行緒永遠阻塞的時候,就會觸發這樣的問題。這種情況不同於其他常見的那種“day-to-day”執行緒問題,例如 lock contention、threads waiting on blocking IO calls等等。真正的lock-ordering deadlock問題可以被看做如下:
Oracle HotSpot 和IBM JVM為大多數的deadlock detectors情況提供瞭解決方案,幫助您快速找出造成這種狀況的罪魁禍首的執行緒。遇到類似lock contention troubleshooting的問題,建議從諸如執行緒轉儲分析為出發點來解決該問題。 一旦找到造成問題的程式碼根源,解決方案涉及lock-ordering條件定址和來自JDK其他可用的併發程式設計技術,如java.util.concurrent.locks.ReentrantLock,提供了諸如tryLock()的方法。這種方法給予開發人員更大的靈活性,也為防止deadlock和thread lock “starvation”提供了更多方式。Clock Time和CPU Burn
在進行JVM調優的同時,也有必要檢查應用程式的行為,更確切地說是最高clock time和CPU burn的貢獻者。
當Java垃圾回收和執行緒併發不再是壓力點,深入到你的應用程式程式碼的執行模式,並專注於頂級響應時間貢獻者(也叫作clock time)是很重要的。檢查應用程式程式碼的消CPU耗和Java 執行緒(CPU burn)也同樣至關重要。CPU使用率較高(>75%)是不正常的(良好的物理資源的利用率)。因為這往往意味著效率低下和容量問題。對於大型的Java EE企業應用,保持安全的CPU緩衝區是必要的,以應對突發的負載衝擊情況。
摒棄那些傳統的跟蹤方法,如在程式碼中加入響應時間“日誌”。Java剖析工具和APM解決方案恰恰可以幫助您分析這型別的問題。這種方式更加高效、可靠。對於Java生產環境缺乏一個強大的APM解決方案。您仍然可以依賴諸如Java VisualVM的工具,通過多個快照進行thread dump分析,並使用OS CPU分析每個執行緒。
最後的建議是,不要妄圖同時解決所有的問題。列出排在最前面的5個clock time和CPU burn問題,然後尋找解決方案。
Application預算
其他關於Java應用程式效能的重要方面是穩定性和可靠性。在有著99.9%典型可用目標的SLA umbrella下,穩定和可靠對於程式的操作尤為重要。這些系統應該具有高容錯級別,並對應用和資源進行嚴格的預算,以防止發生多米諾效應。用這種方法可以防止一些這樣的情況,例如,一個業務流程使用所有可用的物理,中介軟體或JVM資源。
Hot Spots
超時管理
Java application與外部系統之間缺乏合理的超時時間,由於中介軟體和JVM執行緒消耗(blocking IO calls),可能導致嚴重的效能下降和中斷。合理的超時時間可以避免在遇到外部服務提供商速度緩慢的時候,Java執行緒等待太久。
工具
目標 | 建議工具 |
自動、實時地效能監控、調節、預警、趨勢分析、容量管理,等等 |
Enterprise APM solutions(企業級APM解決方案) 注意:APM解決方案提供了工具,這些現成的功能讓您實現以下大部分的Java效能目標。 |
JVM堆和類的元資料的記憶體洩漏分析 | |
JVM記憶體分析和堆容量評估 |
IBM Monitoring and Diagnostic Tools for Java Memory Analyzer (heap dump and application memory footprint analysis) |
JVM和中介軟體併發故障,如thread lock contention和deadlocks |
Oracle Java VisualVM and Oracle Java Mission Control (threads monitoring, thread dump snapshots) jstack, native OS signal such as kill -3 (thread dump snapshots) IBM Monitoring and Diagnostic Tools for Java 注意:強烈推薦大家關注如何執行一個JVM執行緒轉儲分析的相關知識。 |
Java應用程式clock time分析和評測 |
Oracle Java VisualVM and Oracle Java Mission Control (build-in profiler, sampler and recorder) |
Java應用程式和執行緒CPU burn分析 |
Oracle Java VisualVM and Oracle Java Mission Control (CPU profiler) 注意:必要的時候,您還可以依賴JVM執行緒轉儲和OS CPU每個執行緒分析。 |
Java IO和remoting contention分析,包括超時管理評估和調整 |
Oracle Java VisualVM and Oracle Java Mission Control (threads monitoring, thread dump snapshots) jstack, native OS signal such as kill -3 (thread dump snapshots) IBM Monitoring and Diagnostic Tools for Java 注意:強烈推薦大家關注如何執行一個JVM執行緒轉儲分析的相關知識。 |
中介軟體,Java EE容器調整,如執行緒、JDBC資料來源,等等 |
Oracle Java VisualVM and Oracle Java Mission Control (extra focus on exposed Java EE container runtime MBeans) Java EE container administration and management console |
轉自:https://www.evget.com/article/2016/5/17/24105.html