某定时任务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) 这一行。

image.png
image.png

查看支配树,确定大对象

image.png

RingBuffer 这个不知道干啥的,先忽略

arterySchedule_Worker-7 1.73 G 占 22.31%

在支配树这一条目执行 Show Retained Set 可以看到
zbajZbckfkb: 794673 个实例
ZbajAjlc : 176984 个实例

image.png

arterySchedule_Worker-6 1.22 G 占 15.75%

最大头的 3.2 G 到底是啥?

image.png

我先在 dominator_tree 最大的对象 Show Retained Set ,
image.png

然后在 char 上我执行 List Objects->with incoming references 来查看到底是哪些占了这么大空间,然后进行排序,发现有很多长得很像的对象,占用 14.61 MB,然后在 Retained Heap 这一列输入 1 MB.. 16 MB 进行过滤,发现总共有 226 个,结果占用 14.61*226/1024=3.2G

image.png
那就看看这 sql 在干嘛吧

image.png
结果发现,这些 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 的调度,不管前面是否有任务在执行,仍然会开启新任务。现在已经改成有任务未执行完,跳过后续要执行的调度。
如有不正确之处,欢迎指出。

posted @ 2024-10-11 22:33  Code&Fight  阅读(7)  评论(0编辑  收藏  举报