记一次使用easyexcel导入excel导致cpu跑满的问题

记一次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倍的内存使用

最终的疑惑也解决了,项目中使用的方式都是用户模式,这才导致了大量内存的消耗,接下来该想想如何把项目中的读取模式切换成事件模式了,以防其他地方再次出现今天这样的问题。

posted @ 2024-06-03 07:52  翎野君  阅读(99)  评论(0编辑  收藏  举报