讀了啥:JVM記憶體調優

来源:https://www.cnblogs.com/canfactory/p/18009852
-Advertisement-
Play Games

讀了啥 周志明的深入理解Java虛擬機中的調優案例。 第一個案例 背景 一個網站部署在JVM上,而Java堆大小固定在了12G,但是總會出現長時間無法響應的情況。 使用了吞吐量優先收集器:可能是Parallel Scavenge和Parallel Old收集器。 問題 網站直接從磁碟拷貝文檔到堆記憶體 ...


讀了啥

周志明的深入理解Java虛擬機中的調優案例。

第一個案例

背景

一個網站部署在JVM上,而Java堆大小固定在了12G,但是總會出現長時間無法響應的情況。

使用了吞吐量優先收集器:可能是Parallel Scavenge和Parallel Old收集器。

問題

網站直接從磁碟拷貝文檔到堆記憶體中,文檔過大導致進入老年代,頻繁操作很快占滿Java堆,導致Full GC被觸發。

網站以前部署在小記憶體的機器上,反而Full GC造成的停頓不明顯了。所以,如今機器升級意義也不大。

經驗

老年代的占用值得關註,不然Full GC會造成延遲。最起碼程式中的絕大多數對象生存時間不能太長。

因為64位JVM使用到了壓縮指針(像32位JVM一樣管理記憶體,避免記憶體濫用,但需要額外的計算去處理指針)、緩存行對齊(緩存行就是構成電腦高速緩存的基本單位,JVM需要額外計算進行對齊)等原因,性能表現並沒有預期中那樣強於32位JVM。

大記憶體進行快照轉儲難度很大——因為沒地方放,要麼使用JMC(Java任務管理器)監控運行時的記憶體使用狀態。

或者不用大記憶體,改成同時起用多個JVM實例,每個實例擁有小份實際記憶體。並打散上游來的請求,比較平均地分配給每個JVM實例上的應用。在這種情況下,可以用ID標明用戶請求,讓均衡器進行路由,使同個用戶的不同請求固定地在同一臺實例上的應用上處理,也就是所謂集群的親合性。但是,這種情況可能導致共用資源的競爭,如磁碟、資源池等。同時,每個實例使用單獨的緩存也會浪費資源,可以用一個共用緩存池(最典型的就是Redis這種中間件了)來代替。

如何解決

一:改用垃圾收集器。現面世的收集器中有以控制延遲為目標的,如Shenandoah、ZGC等。

二:沿用老的收集器,但改造程式。使其不會快速占滿空間,然後在特定時間點統一Full GC。

在案例中,最終用多個JVM實例,並改用CMS收集器(分多階段,儘量與應用程式併發執行,標記後回收)進行回收。

第二個案例

背景

是一個親合式集群(前文提到過),為了共用數據使用了JBoss(一個開源工具,用於構建分散式的緩存,當然緩存彼此的數據是保持一致的。有點Redis的意思)。

但是總報OOM,且程式沒有發生過頻繁更新。

問題

記憶體中存在大量類型為NAKACK的對象。

JGroups(被JBoss使用,是一個用於模擬分組廣播來實現群組通信的開源工具)有個叫NAKACK的類,它用於記錄協議棧,並實現了up()和down()方法用於表示每層協議在數據包接受和發送時產生的作用

該工具必須保證數據包是有序的,但數據包一定存在失敗重傳的可能。在保證成功前,數據包會一直放在記憶體里。

應用需要控制用戶在某時段只能在一臺機器上登錄(也就是親合性),所以應用中有一個過濾器,它會向所有集群發送"必要信息"以實現這個功能,每來一個請求就這樣做一次。

可想而知,請求越多,必要的信息也就越多。而底層的JGroups又將這些信息存到記憶體里。在網路條件變差時,信息就會堆積,類型NAKACK的對象就越來越多了。

直到OOM。

經驗

使用參數HeapDumpOnOutOfMemoryError在JVM上,這樣如果再發生OOM,會自動生成轉儲文件。

JBoss適合讀操作頻繁的工作,但不太適合寫操作頻繁的場合。

如何解決

這種情況要麼修改"必要信息"的發送方式,要麼更換緩存的框架。

第三個案例

背景

 一個應用運行時報OOM,調大記憶體後繼續報OOM。嘗試在異常發生時拍攝一個“堆轉儲快照”(使用-XX:+HeapDumpOnOutOfMemoryError命令),但發生OOM時依然獲取不到快照文件。

使用jstat(一個JVM自帶的工具,可以用來監控應用運行的記憶體狀態)監控,也沒有發現問題,但OOM還是時而發生。

問題

JVM允許直接堆外空間進行信息處理,但是堆外空間的大小終究還是受到物理記憶體空間的限制。

本應用所在的電腦使用32位系統,最多只能管理4GB大小的記憶體,而案例中更是只擁有2GB記憶體。1.6GB記憶體劃給了JVM堆,只剩下0.4GB記憶體能作為堆外空間使用。

Full GC是正常清理堆外空間的唯一方法,或者在發生OOM後,在Java程式中觸發異常捕獲來執行回收(使用System.gc())。但這兩種情況都不會觸發系統報OOM。

如果應用恰好打開了-XX:+DisableExplicitGC,則上述的第二種方法也會失效,OOM就會拋出。在本例中,應用恰好有使用到CometD(一個Web事件的路由匯流排,用於編寫網路應用)給予的庫函數,它會進行大量NIO操作,從而對堆外空間進行了大量占用。

經驗

開發或管理依賴記憶體不充足的應用時,需要關註其記憶體使用的情況。除了上述對堆外空間的使用會造成隱患外,還有幾種情況需要考慮:

  • 線程堆棧
  • Socket緩存區——存在兩個緩存區用於接收和發送網路信息,連接越多,占用的緩存也就越多
  • JNI代碼——對本地方法的調用會間接使用到堆外空間
  • JVM和GC也可能會用到堆外的空間

如何解決

加強對堆外空間的管理,選用這方面性能更好的工具加入到程式中。

第四個案例

背景

一個應用在進行大併發壓力測試時,發現請求響應時間比預期的長。用mpstat(Linux自帶的實時系統監控工具,可以用於查看每個CPU核心的統計數據)查看後發現有其他程式占用了絕大部分的CPU算力,而應用卻只持有一小部分,這是不合理的。

使用dtrace(一個Solaris系統下才有的工具,其特性似乎已被Linux的更高版本所吸收)檢查系統調用對CPU算力的占用份額,發現份額最多的時fork調用,該調用用於產生新進程。

問題

通過Runtime.getRuntime().exec(),該應用主動調用了Shell腳本,而調用的過程涉及到了進程的創建:複製當前進程-新進程執行命令-退出新進程。反覆操作導致fork調用頻發,從而占用了CPU算力。

經驗

儘量不要有直接調用OS的命令的操作,尤其是不要頻繁進行這種操作。

如何解決

應用改用Java的API來實現相同功能,解決了問題。

第五個案例

背景

一個MIS(信息管理系統)發生了JVM自動關閉的情況,並且在關閉前發生過大量異常,異常顯示的是網路連接斷開。

問題

該MIS與一個OA(辦公自動化系統)系統通過網路交互,並且MIS使用了非同步調用的方式去調用的OA系統的服務。但是OA系統的處理速度很慢,而MIS創建完一次調用需要的資源(主要是線程和Socket連接)後,資源就會一直待在MIS使用的JVM記憶體里。

隨著調用越來越多,資源在JVM記憶體里堆積得也越來越多,JVM會頻繁面臨對這些資源堆滿記憶體時的臨界處理,直到完全崩潰。

經驗

使用通信中間件,使服務調用方和服務方進行解耦,避免直接調用帶來的性能問題。

如何解決

改用消息隊列來實現OA系統與MIS之間的交互。

第六個案例

背景

一個應用所在的JVM在Minor GC(指對新生代進行的垃圾收集)時,會出現500毫秒的停頓,一般來說對於網路服務,停頓毫秒數在兩位數左右才比較能讓人接受。

該應用所在的JVM使用的收集器是ParNew+CMS(在JVM的啟動參數里開啟這兩個收集器的使用,並配置參數規定其執行)。

問題

應用每十分鐘載入文件進行分析,會形成一個HashMap,該HashMap至少有100萬個元素在內。

對GC日誌進行觀察,發現Minor GC發生的前後對記憶體的占用變化不大,說明有效資源比無效資源要多得多。

ParNew使用的是複製演算法,這個演算法會對正在被引用的新生代資源進行複製操作,可想而知在這樣的資源很多的時候,複製操作會占用很多時間。且在生命周期未到達可以進入老年代之前,這樣的複製會一直進行。

經驗

調整JVM參數,讓資源在第一次Minor GC後直接進老年代,等到Major GC時再清理它們。

使用-XX:SurviviorRatio=65536(survivor區和eden區的比例為1:65536,則survivor區幾乎無法使用),-XX:MaxTenuringThreshold=0(設置堆內資源複製進其他區的次數超過設定值時,就直接進入老年代。設定值為0,說明第一次複製就可以直接進入老年代),或-XX:+Always-Tenure(也是第一次複製就進入老年代)。右側命令任選其一。

不過以上的做法只是權宜之計。

調整程式數據結構,降低數據結構的占用空間。在本例中,主要還是因為HashMap的空間效率有限導致的,在鍵值對均為long類型的情況下進行了包裝,導致冗餘度極高。

對於基本類型的數據不要有過度包裝,尤其是在其數量很多時。

如何解決

使用空間利用效率更高的數據結構。

第七個案例

背景

一個Windows上的桌面程式,發現偶爾會出現一分鐘左右的停頓。

問題

停頓由GC造成,在加入-XX:+PrintReferenceGC參數後進行觀察時發現準備收集到開始收集的階段占據了絕大部分的回收時間。

進一步發現該程式在最小化時,工作記憶體被交換進磁碟空間。從而可能在GC時再次發生交換,而IO操作就會花費大量時間。

經驗

調試時可以加入-XX:+PrintGCApplicationStoppedTime -XX:+PrintGCDateStamps -Xloggc:gclog.log,這裡是三個獨立的參數,前兩個分別用來列印GC的停頓時長和發生時刻,而第三個將列印出來的信息輸入到文件中。

如何解決

加入參數-Dsun.awt.keepWorkingSetOnMinimize=true,該參數允許Java程式在最小化時也可以占有工作記憶體,這一點也可以被應用在各種Java編輯器上。

第八個案例

背景

一個應用用於離線分析任務,設定了-XX:MaxGCPauseMillis=500ms(GC會儘可能將時間壓縮在這個限制下,但不保證)。但分析日誌發現實際運行發現停頓在3000ms以上,且回收的操作只占了幾百ms。

問題

應用記憶體在多個應用線程,只有當所有應用線程都抵達安全點(JVM根據自身機制設定的,勒令全部線程暫停工作的時刻)後,GC才開始。

檢查應用線程抵達安全點的狀態,發現部分線程抵達安全點後,仍在等待其他很慢的應用線程抵達安全點。按理說,那些"很慢的線程"中的安全點應該設置得妥當,才能保證即時暫停下來。

對於JVM而言,它會尋找線程的程式中可以放置安全點的地方進行放置,且存在以下規則:對於一個迴圈體,如果該迴圈是可數迴圈(以int類型或更小類型作為索引),則內部不會有安全點;如果該迴圈是不可數迴圈(以long類型或者更大類型作為索引),則內部存在安全點。

對應用的代碼進行檢查,發現那段代碼中恰好有一處使用int類型作為索引的迴圈,但迴圈的操作涉及網路連接,耗時很多。

經驗

分析GC時,應該對GC總時間和GC實際工作時間進行對比,這兩個值相近才是比較合理的。如果相差過大(一般只會是總時間>>實際工作時間),說明GC的準備工作中存在問題。

分析GC可以從分析安全點入手來瞭解情況,主要使用參數-XX:+PrintSafepointStatistics-XX:PrintSafepointStatisticsCount=1來記錄安全點的一些信息(同時使用兩個參數)。主要是觀察在存線上程抵達安全點時,是否有尚未抵達安全點的線程。

一般來說,它的信息會統計各類狀態的線程數,在日誌中體現為[threads: total(STW發生時的線程總數) initially_running(STW發生時正在運行的線程數) wait_to_block(STW發生時需要堵塞的線程數,這部分線程就正在執行,直到抵達安全點)]

也會記錄時間的使用情況[time: spin(GC線程自旋花費的時間,也就是在等待工作線程堵塞花費的時間) block sync cleanup vmop]。

如果要找尋線程的具體信息,可以通過參數-XX:+SafepointTimeout-XX:SafepointTimeoutDelay=指定值來進行,前者指定JVM對線程在約定進入安全點的時刻之後,延遲一段時間進入會觸發超時記錄,後者指定這個延遲的具體值。如果觸發記錄,則線程的具體信息會被記錄下來,可供進一步調查。

對於迴圈體中執行重型操作(如處理網路連接)中,索引可以修改成long類型或以上,方便安全點在迴圈執行中設置。

如何解決

將該處迴圈的索引設置為long類型,問題解決。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本篇文章將帶大家運行 Flink 最簡單的程式 WordCount。先實踐後理論,對其基本輸入輸出、編程代碼有初步瞭解,後續篇章再對 Flink 的各種概念和架構進行介紹。 下麵將從創建項目開始,介紹如何創建出一個 Flink 項目;然後從 DataStream 流處理和 FlinkSQL 執行兩種... ...
  • 背景 項目當中需要進行業務時間的校驗,如上午 9:00-下午 17:00,在 9:00 前或 17:00 後是不能處理相關業務的。因此在業務校驗的 Service 中定義了一個 checkBizTime() 方法。原本代碼如下: public void checkBizTime() { Date c ...
  • 本文介紹在Visual Studio軟體中配置、編譯C++環境下matplotlibcpp庫的詳細方法。 matplotlibcpp庫是一個C++環境下的繪圖工具,其通過調用Python介面,實現在C++代碼中通過matplotlib庫的命令繪製各類圖像。由於其需要調用Python介面,因此在配置m ...
  • 曾經有一位魔術師,他擅長將Spring Boot和Redis這兩個強大的工具結合成一種令人驚嘆的組合。他的魔法武器是Redis的Lua腳本。 今天,我們將揭開這個魔術師的秘密,探討如何在Spring Boot項目中使用Lua腳本,以解鎖新的可能性和提高性能。如果你一直在尋找提升你的應用程式的方法,那 ...
  • 如何在運行主方法的同時非同步運行另一個方法,我是用來更新緩存; 1. 工具類 public class ThreadPoolUtils { private static final Logger LOGGER = LoggerFactory.getLogger(ThreadPoolUtils.clas ...
  • class_2 構造函數 構造函數是一種特殊的成員函數,用於創建和初始化類的對象。它的名稱與類的名稱相同,沒有返回值,也不需要顯式調用。在C++中,每個類都必須至少有一個構造函數。 當我們創建一個類的對象時,編譯器會自動調用構造函數來初始化該對象的成員變數。構造函數可以執行一些操作,如初始化成員變數 ...
  • Java Math Java 的 Math 類 擁有許多方法,允許您在數字上執行數學任務。 常用方法: Math.max(x, y): 找到 x 和 y 的最大值 Math.min(x, y): 找到 x 和 y 的最小值 Math.sqrt(x): 返回 x 的平方根 Math.abs(x): 返 ...
  • 一、java鎖存在的必要性 要認識java鎖,就必須對2個前置概念有一個深刻的理解:多線程和共用資源。 對於程式來說,數據就是資源。 在單個線程操作數據時,或快或慢不存在什麼問題,一個人你愛乾什麼乾什麼。 多個線程操作各自操作不同的數據,各乾各的,也不存在什麼問題。 多個線程對共用數據進行讀取操作, ...
一周排行
    -Advertisement-
    Play Games
  • 最近做項目過程中,使用到了海康相機,官方只提供了C/C++的SDK,沒有搜尋到一個合適的封裝了的C#庫,故自己動手,簡單的封裝了一下,方便大家也方便自己使用和二次開發 ...
  • 前言 MediatR 是 .NET 下的一個實現消息傳遞的庫,輕量級、簡潔高效,用於實現進程內的消息傳遞機制。它基於中介者設計模式,支持請求/響應、命令、查詢、通知和事件等多種消息傳遞模式。通過泛型支持,MediatR 可以智能地調度不同類型的消息,非常適合用於領域事件處理。 在本文中,將通過一個簡 ...
  • 前言 今天給大家推薦一個超實用的開源項目《.NET 7 + Vue 許可權管理系統 小白快速上手》,DncZeus的願景就是做一個.NET 領域小白也能上手的簡易、通用的後臺許可權管理模板系統基礎框架。 不管你是技術小白還是技術大佬或者是不懂前端Vue 的新手,這個項目可以快速上手讓我們從0到1,搭建自 ...
  • 第1章:WPF概述 本章目標 瞭解Windows圖形演化 瞭解WPF高級API 瞭解解析度無關性概念 瞭解WPF體繫結構 瞭解WPF 4.5 WPF概述 ​ 歡迎使用 Windows Presentation Foundation (WPF) 桌面指南,這是一個與解析度無關的 UI 框架,使用基於矢 ...
  • 在日常開發中,並不是所有的功能都是用戶可見的,還在一些背後默默支持的程式,這些程式通常以服務的形式出現,統稱為輔助角色服務。今天以一個簡單的小例子,簡述基於.NET開發輔助角色服務的相關內容,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 第3章:佈局 本章目標 理解佈局的原則 理解佈局的過程 理解佈局的容器 掌握各類佈局容器的運用 理解 WPF 中的佈局 WPF 佈局原則 ​ WPF 視窗只能包含單個元素。為在WPF 視窗中放置多個元素並創建更貼近實用的用戶男面,需要在視窗上放置一個容器,然後在這個容器中添加其他元素。造成這一限制的 ...
  • 前言 在平時項目開發中,定時任務調度是一項重要的功能,廣泛應用於後臺作業、計劃任務和自動化腳本等模塊。 FreeScheduler 是一款輕量級且功能強大的定時任務調度庫,它支持臨時的延時任務和重覆迴圈任務(可持久化),能夠按秒、每天/每周/每月固定時間或自定義間隔執行(CRON 表達式)。 此外 ...
  • 目錄Blazor 組件基礎路由導航參數組件參數路由參數生命周期事件狀態更改組件事件 Blazor 組件 基礎 新建一個項目命名為 MyComponents ,項目模板的交互類型選 Auto ,其它保持預設選項: 客戶端組件 (Auto/WebAssembly): 最終解決方案裡面會有兩個項目:伺服器 ...
  • 先看一下效果吧: isChecked = false 的時候的效果 isChecked = true 的時候的效果 然後我們來實現一下這個效果吧 第一步:創建一個空的wpf項目; 第二步:在項目裡面添加一個checkbox <Grid> <CheckBox HorizontalAlignment=" ...
  • 在編寫上位機軟體時,需要經常處理命令拼接與其他設備進行通信,通常對不同的命令封裝成不同的方法,擴展稍許麻煩。 本次擬以特性方式實現,以兼顧維護性與擴展性。 思想: 一種命令對應一個類,其類中的各個屬性對應各個命令段,通過特性的方式,實現其在這包數據命令中的位置、大端或小端及其轉換為對應的目標類型; ...