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