記一次poi匯入excel引起cpu跑滿的問題
生產應用機器配置:8C 16G
週日突然收到告警,cpu持續15分鐘空閒時間小於10%,趕緊聯絡運維要日誌,透過分析dump_high_cpu
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
28830 jbossuse 20 0 12.9g 6.9g 27m R 100.9 44.6 16:12.49 java
29157 jbossuse 20 0 12.9g 6.9g 27m R 100.9 44.6 11:43.56 java
29991 jbossuse 20 0 12.9g 6.9g 27m R 100.9 44.6 14:31.26 java
30187 jbossuse 20 0 12.9g 6.9g 27m R 100.9 44.6 14:06.83 java
30408 jbossuse 20 0 12.9g 6.9g 27m R 100.9 44.6 13:02.85 java
30599 jbossuse 20 0 12.9g 6.9g 27m R 100.9 44.6 12:44.31 java
28840 jbossuse 20 0 12.9g 6.9g 27m R 99.1 44.6 15:44.46 java
29165 jbossuse 20 0 12.9g 6.9g 27m R 95.5 44.6 15:37.87 java
27522 jbossuse 20 0 12.9g 6.9g 27m S 0.0 44.6 0:00.00 java
27523 jbossuse 20 0 12.9g 6.9g 27m S 0.0 44.6 0:00.68 java
看出來,有8個執行緒跑到100%.透過pid去尋找對應java core 裡的執行緒,發現這個8個執行緒都是進行同一個操作
"default task-17" prio=10 tid=0x00007ff15802f000 nid=0x71ed runnable [0x00007ff1f10ac000]
java.lang.Thread.State: RUNNABLE
at org.apache.xmlbeans.impl.store.Locale.count(Locale.java:2049)
at org.apache.xmlbeans.impl.store.Xobj.count_elements(Xobj.java:2050)
at org.openxmlformats.schemas.spreadsheetml.x2006.main.impl.CTColsImpl.sizeOfColArray(Unknown Source)
- locked <0x00000006e8d5c800> (a org.apache.xmlbeans.impl.store.Locale)
at org.apache.poi.xssf.usermodel.helpers.ColumnHelper.addCleanColIntoCols(ColumnHelper.java:115)
at org.apache.poi.xssf.usermodel.helpers.ColumnHelper.cleanColumns(ColumnHelper.java:56)
at org.apache.poi.xssf.usermodel.helpers.ColumnHelper.<init>(ColumnHelper.java:43)
at org.apache.poi.xssf.usermodel.XSSFSheet.read(XSSFSheet.java:144)
at org.apache.poi.xssf.usermodel.XSSFSheet.onDocumentRead(XSSFSheet.java:130)
at org.apache.poi.xssf.usermodel.XSSFWorkbook.onDocumentRead(XSSFWorkbook.java:286)
at org.apache.poi.POIXMLDocument.load(POIXMLDocument.java:159)
at org.apache.poi.xssf.usermodel.XSSFWorkbook.<init>(XSSFWorkbook.java:186)
at org.apache.poi.ss.usermodel.WorkbookFactory.create(WorkbookFactory.java:73)
定位到佔滿cpu的操作,是用poi匯入execl ,找到程式碼處,發現已經限制了匯入的excel的大小為1MB,但是沒有限制匯入頻率,這樣的話,使用者短時間內可以頻繁匯入資料到系統.
那麼問題來了,頻繁匯入1MB的excel為什麼會導致cpu跑滿?拉取了gc日誌發現jvm在頻繁的ygc,平均幾秒就發生一次.並且在分析問題的這段時間,cpu仍然沒有下降,佔用cpu高的執行緒仍然在持續,會不會是這幾個執行緒在建立大量物件,導致ygc頻繁回收,而且回收的年輕代空間仍然不滿足執行緒的需要,進而引發cpu跑滿?
這是一個很合理的猜測,但是需要事實來證明,在確認這臺機器無法自行恢復之後,聯絡運維先拉取了dump檔案,然後重啟機器.
經過了漫長的等待,終於到手了dump檔案,分析dump檔案後發現,有大量的char[] 和list物件在生成.這個和猜測以及本身定位到的poi程式碼處實現一致.
最終問題定位後的描述如下:
在某個業務場景,報表匯入沒有頻次限制,導致使用者可以重複高頻次的匯入excel到系統,導致系統在用poi解析時,生成了大量的物件,並且poi在最終彙總物件時加了鎖,jvm年輕代在回收多次之後仍然不滿足執行緒所需,引發鎖自旋,導致cpu跑滿.
問題定位出來了,但是還有一點疑惑,為什麼1MB的物件在生產poi物件時,會佔用更多的記憶體呢?
原來,poi讀取excel有兩種方式,一種是使用者模式,另外一種是事件模式。使用者有封裝好的方法,使用簡單,但是會建立非常多的物件,耗記憶體,後者用來讀取excel,但不用把整個excel載入到記憶體,減少了至少10倍的記憶體使用
最終的疑惑也解決了,專案中使用的方式都是使用者模式,這才導致了大量記憶體的消耗,接下來該想想如何把專案中的讀取模式切換成事件模式了,以防其他地方再次出現今天這樣的問題。