關於GC(上):Apache的POI組件導致線上頻繁FullGC問題排查及處理全過程

来源:https://www.cnblogs.com/wuyuegb2312/archive/2019/11/11/11799352.html
-Advertisement-
Play Games

一次線上頻繁FullGC問題的排查和解決記錄,整理了一下通用的排查解決過程,同時介紹了一些可能會用到的工具。 ...


某線上應用在進行查詢結果導出Excel時,大概率出現持續的FullGC。解決這個問題時,記錄了一下整個的流程,也可以作為一般性的FullGC問題排查指導。

1. 生成dump文件

為了定位FullGC的原因,首先需要獲取heap dump文件,看下發生FullGC時堆記憶體的分配情況,定位可能出現問題的地方。

1. 1 通過JVM參數自動生成

可以在JVM參數中設置-XX:+ HeapDumpBeforeFullGC參數。
建議動態增加這個參數,直接線上上鏡像中增加一方面是要重新打包發佈,另一方面風險比較高

sudo -u admin /opt/taobao/java/bin/jinfo -flag +HeapDumpBeforeFullGC pid
sudo -u admin /opt/taobao/java/bin/jinfo -flag +HeapDumpAfterFullGC pid

也可以用HeapDumpOnOutOfMemoryError這個參數,只在outOfMemoryError發生時才dump。實測只有在fullgc完成時才會產生該文件,fullgc期間看不到。
此外還需要-XX:HeapDumpPath=/home/admin/logs/java.hprof這個參數來指定dump文件存放路徑。

1.2 通過JDK工具生成

1.2.1 jmap

先獲取java進程ID,再使用jmap進行dump。
註意,虛擬機上的jmap可能沒有做路徑映射,需要手動選擇jdk路徑下來執行

ps -aux | grep java
jmap -dump:file=test.hprof,format=b XXXX

1.2.2 通過jcmd

JDK7後新增的多功能命令,其中jcmd pid GC.heap_dump FILE_NAME的效果和jmap -dump:file=test.hprof,format=b pid一樣。

1.3 JConsole

可以生成本機或遠程JVM的dump。還有一些其他工具就不詳細介紹了。

2. 下載dump文件

由於使用的是阿裡雲的伺服器,可以直接將dump文件上傳到OSS上通過公司內部工具來分析,或通過OSS再下載到本地。
設置OSSCMD:
操作命令 osscmd config --host=oss-cn-hangzhou-am101.aliyuncs.com --id=** --key=**
創建bucke:osscmd cb 000001
上傳文件:osscmd put 1.txt oss://000001/
下載文件:osscmd get oss://000001/1.txt 1.txt

其他類型的Linux主機可以使用SCP命令,參考:Linux scp命令

3. 分析工具

通過dump文件來分析fullGC的原因,需要關註哪些類占用記憶體空間較多、不可到達類等。
由於使用的是公司內部工具Zprofiler和grace,詳細的使用過程這裡就不截圖了。一些其他可用的工具和命令(參考Java記憶體泄漏分析系列之六:JVM Heap Dump(堆轉儲文件)的生成和MAT的使用):

  • jhat, JDK自帶,使用jhat <heap-dump-file>生成網頁,通過瀏覽器訪問``查看
  • jvisualvm
  • Eclipse Memory Analyzer(MAT)
  • IBM Heap Analyzer

需要註意的是,只看dump文件有時還不能得到結論,因為占用空間大頭的有可能是String、ArrayBlockingList這樣的對象,而且內容可能是null或null對象的集合,無從排查。此時還要結合發生fullgc前後業務系統發生了什麼動作來確定。如果有條件的話可以在日常環境或預發環境重現一下。
當然,如果記憶體中的空間消耗對象是特殊的類,就比較好排查了。

4. 分析和改進

具體情況具體分析。

4.1 本次排查的場景

查詢DB中數據->在非同步線程中通過poi轉換成Excel->上傳到OSS。

示例代碼:

// 導出代碼中將變數直接作為lambda表達式的值傳入
List<XXData>  data = queryData(request);
SheetDownloadProperty property = sheetDownloadProperties.get(0);
property.setTotalCount(request.getQueryRequest().getPageSize());
property.setPageSize(request.getQueryRequest().getPageSize());
property.setQueryFunction((currentPage, pageSize) ->  data);
// 該組件會線上程池非同步調用poi組件轉換為excel、上傳OSS、下載
asyncDownloadService.downloadFile(downloadTask);
private List<XXData> queryData(ExportRequest request) {
    //查詢DB,略
}
// 查詢方法
@FunctionalInterface
public interface PageFunction<T> {

    /**
     * 方法執行
     */
    List<T> apply(Integer currentPage,Integer pageSize);
}

4.2 dump文件分析

通過內部工具可見,fullGC前有三個占據記憶體較高的ArrayBlockingList,裡面有大量的內容為null的Object。

這三個ArrayBlockingList所屬的中間件,雖然本身和業務流程沒有關係,但是仍不能排除嫌疑。

4.3 嘗試解決

4.3.1 方案1:poi相關解決方案

由於依賴了二方庫poi,這個庫的usermodel模式很容易引起fullGC,同時也懷疑是因為lambda表達式直接傳了變數。
把poi的usermodel改為事件模式(https://my.oschina.net/OutOfMemory/blog/1068972)可以避免這個問題。
但是該功能是一個二次封裝的三方包中的,同時其他引用該組件的應用fullgc頻率並不高,沒有採用這個方案。

4.3.2 方案2:中間件升級

持有大量null對象的中間件版本較低,且新版目前已不再維護,老版本的releas note雖然沒有提到這條bug fix,有一定嫌疑。
該中間件初始化時會創建三個容量為810241024的ArrayBlockingList,和dump文件相符合。
同樣是因為這個中間件是在三方包中封裝,不方便直接該版本,同樣沒有採用這個方案。

4.3.3 方案3:增大堆大小

可以調整metaspace參數來實現,本次想找到代碼中相關的線索來解決,未採用該方案。

4.3.4 方案4:業務代碼修改

仔細觀察了這段代碼在其他系統的的實現,發現其他系統的lambda表達式是匿名方法,而不是直接傳值,即:

property.setQueryFunction((currentPage, pageSize) ->  {
    // 查詢邏輯, 略
);

懷疑是直接傳變數進去導致的垃圾回收問題。更改到這種模式後,觸發下載功能時,連續長時間的fullGC仍然時有發生,沒有解決問題。

4.3.5 方案5:替換垃圾回收器

暫時能確定的原因是,公司中間件本身占用堆記憶體較多,運行poi增加了GC的頻率。但是由於它們都在二方庫的原因,不方便修改。
此時搜索到stackoverflow有關於poi反覆GC的一個問題,和我的情況類似,也是反覆GC但是仍然不能釋放記憶體。有回覆建議將GC回收器替換為G1GC,將預設的UseConcMarkSweepGC替換後效果明顯,一次FullGC就可以完成回收釋放,不會反覆FullGC,如下圖,20:30前的fullGC是CMS,持續時間長且反覆進行;20:30後是替換後第一次觸發excel轉換下載,進行了多次下載,即使發生FullGC也只有1次,大大緩解了之前的問題:

本次暫定只採用方案5。

G1GC在JDK9已替代CMS成為了正式的垃圾回收器,低版本JDK需要手動設置。具體需要設置的JVM參數:

-Xms32m
-Xmx1g
-XX:+UnlockExperimentalVMOptions
-XX:+UseG1GC 
-XX:MaxHeapFreeRatio=15 
-XX:MinHeapFreeRatio=5

註意前兩行一般應用都會設置,不要覆蓋掉。最後兩行需要視情況調整。另外,預設的-XX:+UseConcMarkSweepGC需要去掉。

使用G1GC時需要確認工作線程數是否和預期一致,不要太多,一般來說和CPU核數一致即可。出現非預期數目的原因可能是,鏡像腳本指定核數時,直接按照物理機而不是虛擬機核數來生成。
查看方式是看gc日誌:

虛擬機設置核數的dokcker腳本示例:

export CPU_COUNT="$(grep -c 'cpu[0-9][0-9]*' /proc/stat)"

5. 其他

5.1 典型fullGC場景舉例

  • 外部資源未釋放,如將利用tair實現的分散式鎖放在Map中,未做解鎖
  • fastjson的反序列化異常拋出後沒有處理
  • 框架固有缺陷,如本例apache的poi組件,使用usermodel模式做excel導出時,當操作比較頻繁或有其他記憶體泄漏有可能造成
  • JVM的metaspace設置過小

5.2 core dump和heap dump

core dump是針對線程某一時刻的運行情況的,可以看到執行到哪個類哪個方法哪一行以及執行棧的;heap dump是針對記憶體某一時刻的分配情況的。

5.3 stackoverflow上關於poi記憶體占用問題的討論:

簡單摘譯了一些,可以直接看原文。

  1. Java對堆記憶體分配是懶回收的,如果JVM不想這麼做,即使運行Runtime.gc(),也可能什麼也不做。sapiensl和Amongalen的回答
  2. 觸發FullGC,並不是因為記憶體泄漏,僅僅是因為poi占用了太多的記憶體。Michael的回答

關於G1GC,會在後續文章中研究。


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

-Advertisement-
Play Games
更多相關文章
  • 1.什麼是window? window:是一個全局對象, 代表瀏覽器中一個打開的視窗, 每個視窗都是一個window對象2.什麼是document? document是window的一個屬性, 這個屬性是一個對象 document: 代表當前視窗中的整個網頁, document對象保存了網頁上所有的 ...
  • 本節說一下DOM操作模塊里的複製元素子模塊,該模塊可以複製一個DOM節點,並且可選擇的設置是否複製其數據緩存對象(包含事件信息)和是否深度複製(子孫節點等),API如下: $.clone(elem, dataAndEvents, deepDataAndEvents) ;jQuery底層方法,返回DO ...
  • 場景 Nginx配置實例-負載均衡實例:平均訪問多台伺服器: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/103019576 在上面實現了負載均衡的簡單實例,平均訪問兩個伺服器。 但是如果不想使其平均訪問,有哪些常用的分配伺服器 ...
  • 場景 Nginx配置實例-反向代理實例:根據訪問的路徑跳轉到不同埠的服務中: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/102963715 在上個實例中,搭建好兩台tomcat伺服器。 負載均衡實現效果 瀏覽器輸入訪問地址, ...
  • Map介面概述: 現實生活中,我們常會看到這樣的一種集合:IP地址與主機名,身份證號與個人,系統用戶名與系統用戶對象等, 這種一一對應的關係,就叫做映射。Java提供了專門的集合類用來存放這種對象關係的對象,即 java.util.Map 介面 Map介面與Collection介面的區別: Coll ...
  • 一、貪婪和非貪婪 1.貪婪:儘可能多的匹配,(*)表示貪婪匹配 2.非貪婪:找到符合條件的最小內容即可,(?)表示非貪婪 3.正則預設使用貪婪匹配 import re title = u"<div>name</div><div>age</div>" p1 = re.compile(r"<div>.* ...
  • 很多伙伴對 Python 的迭代器、可迭代對象、生成器這幾個概念有點搞不清楚,我來說說我的理解,希望對需要的朋友有所幫助。 1 迭代器協議 迭代器協議是核心,搞懂了這個,上面的幾個概念也就很好理解了。 所謂迭代器協議,就是要求一個迭代器必須要實現如下兩個方法 Return the iterator ...
  • 分頁查詢在網頁中隨處可見,那原理是什麼呢?下麵簡單介紹一下基於MySql資料庫的limit實現方法。 首先明確為什麼要使用分頁查詢,因為數據龐大,查詢不可能全部顯示在頁面上,如果全部顯示在頁面上,也會造成查詢速度慢的情況,所以分頁查詢解決了①數據查詢;②性能優化,等(其他問題歡迎補充)的問題。 分頁 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...