据实施人员交代,这个巨型项目在2020-2021年平稳运行了一年,元旦之后,频繁报内存溢出,有时几天一次,有时一天几次,完全摸不到规律。而且堆内存已设置最大4G。OK,开整!
首先,堆转储文件已不可得,在weblogic增加jvm参数,下次OOM时即可获得堆转储文件。
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath
一边等问题复现,一边从程序的角度排除。程序有无定时任务?有!但挨个看了,quartz的定时任务中,并不存在会导致内存溢出的代码,至少看起来是这样。然后看Oracle的Job,执行的存储过程有无问题,依旧没有问题。
从日志方面排查,weblogic日志中,有很多报线程超时未处理完的错误。当然,这种日志只能提供参考,并不一定是内存溢出的根本原因。亦可能内存将满之后,其它线程在处理时,JVM无法GC,没有空间创建对象,而导致线程一直阻塞中。
more than the configured time (StuckThreadMaxTime) of "600" seconds. Stack trace:
java.lang.Object.wait(Native Method)
java.lang.Object.wait(Object.java:485)
java.io.ObjectStreamClass$EntryFuture.get(ObjectStreamClass.java:369)
java.io.ObjectStreamClass.lookup(ObjectStreamClass.java:303)
java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:545)
java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1599)
java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1494)
java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1748)
java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1327)
java.io.ObjectInputStream.readObject(ObjectInputStream.java:349)
net.XXXXXXXXXX.YYYYYY.cached.cache.Caches.unserialize(Caches.java:108)
再看项目本身日志,记录信息也有限,而且打印的堆栈也不一定是内存泄漏的方法。
[ERROR][2022-01-01 12:32:54][SYSTEM]java.lang.OutOfMemoryError: GC overhead limit exceeded
然后连续几天观察jvisualvm工具的信息,发现堆内存占用在0.3G到1.5G以30分钟间隔,呈周期性升降。
完全找不到规律,看来只能等事件发生了。
果然,11月13号(上一次是11月2号),终于出现OOM错误,直接取文件观之(5G之巨)。
首先,采用JVM自带的jvisualvm工具导入,但一直加载不了。然后采用IBM HeapAnalyzer工具,甚至在启动工具时加了参数 Java -xmx8000m -jar *.jar,加载了十分钟,进度定格在59%,然后直接无响应状态了,无奈又作罢。
接着,换了一款收费工具JProfiler,这款工具那是相当强大,不到3分钟就加载好了堆转储文件。
看上面几张关键截图,其实问题已经很明显了,几个自定义RowSet对象竟然占据了数G的内存空间。结合源码分析,发现是一个SQL中查出来的数据量达66万行之大。之前正常,为何忽然巨大呢?因为三方直连项目的数据库,执行了错误的脚本,生成了巨量的表数据,导致查询过载,rowset对象在put数据时,内存溢出。
给现场支方案:1、停止三方脚本运行;2、删除重复数据。
--去除重复数据脚本
--1.全量
create table AAA_BACK as select * from AAA;
--2.临时
create table AAA_TEMP as select * from AAA where SCSPF_NO in (select min(SCSPF_NO) from scspfmst group by org_no,pla_no,crw_no,SCSFT_DAT,scsft_no);
--3.全删
truncate table AAA;
--4.复制
insert into AAA select * from AAA_TEMP;
commit;
--5.去除
drop table AAA_TEMP;
--6.查验
select * from ( select count(1) as count,SCSFT_DAT from AAA group by SCSFT_DAT );
其实,排查的过程并不尽如上面那么简单和直接,其中碰到了不少钉子,走了一些弯路,包括增加对JVM底层的学习等,最终的结果还是好的。