某定时任务OOM排查
现场定时任务 OOM,堆转储文件 8 G,先打成 tar. gz,再压缩一次,才得以传输过来。
工具-MAT (MemoryAnalyzer)
MAT下载地址
当前启动需要 JDK 17: JDK 17 下载地址 好像也可以使用 JDK 8,有兴趣的可以自己研究下
如果 dump 出来的 hprof 文件过大,比如这个文件 8 G,需要改下 MemoryAnalyzer. ini 文件。
-vmargs
--add-exports=java. base/jdk. internal. org. objectweb. asm=ALL-UNNAMED
-Xmx 8192 m
List Objects
outgoing references: 当前对象包含的引用
incoming references: 引用当前对象的引用
Shallow Heap 和 Retained Heap
个人理解 Shallow Heap 就是当前对象,不包含它含有的引用的值大小
Retained Heap 就是包含了自己及引用的大小,默认显示单位为 byte. 可以在 window->preference->Memory Analyzer -> Bytes Display 选项,选择 Smart, 可智能显示 KB, MB, GB。
Histogram 直方图
展示每个 class 实例的数量
Dominator Tree 支配树
展示存活的大对象
分析
查看 Leak Suspects
发现 arterySchedule_Worker-7 和 arterySchedule_Worker-6 都在 zbajZbckfkbService.batchUpdateZbajZbckfkb(zbajZbckfkb)
这一行。
查看支配树,确定大对象
RingBuffer 这个不知道干啥的,先忽略
arterySchedule_Worker-7 1.73 G 占 22.31%
在支配树这一条目执行 Show Retained Set
可以看到
zbajZbckfkb: 794673 个实例
ZbajAjlc : 176984 个实例
arterySchedule_Worker-6 1.22 G 占 15.75%
最大头的 3.2 G 到底是啥?
我先在 dominator_tree 最大的对象 Show Retained Set
,
然后在 char 上我执行 List Objects->with incoming references 来查看到底是哪些占了这么大空间,然后进行排序,发现有很多长得很像的对象,占用 14.61 MB,然后在 Retained Heap 这一列输入 1 MB.. 16 MB
进行过滤,发现总共有 226 个,结果占用 14.61*226/1024=3.2G
那就看看这 sql 在干嘛吧
结果发现,这些 sql 基本都是 SELECT c_ahdm,n_yckcs FROM db_zbaj.t_zdzck_hzb\u000a WHERE c_ahdm in (xxx)
,结果发现在循环调用的方法 buildAjlcData 中有
Map<String, Object> tccsMap = zbajAjlcService.getTccs (ajlcAjbs);
根据已有信息分析代码
private void getUpdateAjckAndAjlc() {
//查控反馈信息
List<ZbajAjck> ajckCkfk = zbajAjckService.getCkfkAndAjck();
Map<String, Object> tmpCkfk = new HashMap<>();
List<String> ahdm = new ArrayList<>();
Set<String> ajlcAjbs = new HashSet<>();
//.....
if (CollectionUtils.isNotEmpty(ahdm)) {
//获取要更新的案件流程表
List<ZbajAjlc> zdzckAjlc = zbajAjlcService.selectAjlcByAhdm(ajlcAjbs);
//获取要更新的查控反馈表
Set<ZbajZbckfkb> zbajZbckfkb = zbajZbckfkbService.getZbajZbckfkbByAjbsL(ahdm);
List<String> ajckbhL = new ArrayList<>();
for (ZbajZbckfkb ckfkxx : zbajZbckfkb) {
// ...
buildAjlcData(ajlcAjbs, zdzckAjlc, ckfkxx);
}
}
}
private void buildAjlcData(Set<String> ajlcAjbs, List<ZbajAjlc> zdzckAjlc, ZbajZbckfkb ckfkxx) {
Map<String, Object> tccsMap = zbajAjlcService.getTccs(ajlcAjbs);
for (ZbajAjlc zbajAjlc : zdzckAjlc) {
String ajbs = zbajAjlc.getAjbs();
if (tccsMap.containsKey(ajbs)) {//设置统查次数
Integer tccs = (Integer) tccsMap.get(ajbs);
zbajAjlc.setCccxcs(tccs == null ? 0 : tccs);
}
if (zbajAjlc.getAjbs().equals(ckfkxx.getAjbs())) {
BigDecimal oldCcje = zbajAjlc.getZdzcczje();
BigDecimal newCcje = ckfkxx.getCcje();
zbajAjlc.setZdzcczje(zbajAjlc.getZdzcczje() == null ? ckfkxx.getCcje() : newCcje.add(oldCcje));
}
zbajAjlc.setZhyccccxywcc(Consts.SFYCC_YCC);//最后一次统查有无财产 1.有;2.无;
zbajAjlc.setZxtcwcccs(Consts.WCC_ONE);//只要财产就将其清空
zbajAjlc.setZdztcycc(1);//总对总统查有财产
}
}
- zbajZbckfkb 有 79 万个实例,执行 79 万次 buildAjlcData,但 ajlcAjbs 是固定的,所以tccsMap 没必要,可以把它提出循环外,由 79 万次-》1 次。
- buildAjlcData 里的 zdzckAjlc循环,会执行 17 万次。
外层 79 万 * 内层 17 万=1600 亿次
。分析发现跟 ckfkxx 有关的就是设置某个金额,因此把该方法拿出循环。由 1600 亿次循环-》17 万次。 - RecordEvent 显示是 artery-monitor 里的代码,由于没有在使用,因此将开关设置为关闭
关于其他的
为什么会有两个线程在执行这一个定义任务?
artery 的调度,不管前面是否有任务在执行,仍然会开启新任务。现在已经改成有任务未执行完,跳过后续要执行的调度。
如有不正确之处,欢迎指出。