生产问题-MetaSpace空间OOM

2020-05-15  本文已影响0人  耳威巴帝

发现故障、

生产服务器某天开始,突然每天不同时间点报MetaSpace的oom,因为本身服务器没有监控,加上是集群部署,又因为疫情访问量减少,导致服务器自动重启情况一直没人知道,发现问题是第三方每天都发现调用会有500才发现此问题。


企业微信截图_15895112925256.png

故障分析过程、

1,第三方反应请求突然500,并且让第三方给出了具体500的时间段
2,到日志中查看发现,在此时间段中,最多的ERROR级别日志就是MetaSpace的OOM
3,通过jps jstat的JVM命令,发现MetaSpace的内存处于一个稳步上升的情况,最后OOM,并且GC后MetaSpace大小并没有减少
4,但是从日志不好定位问题,因为OOM之前的业务代码是正常的,一直走到结束就直接报了OOM,所以无法正确定位到具体问题
5,通过jvisualvm命令打开JVM控制台,看到的MetaSpace大小和jstat -class一致,持续上升,并且堆稳定GC,MetaSpace没有减少,生成dump文件查看,发现有一个类初始化了N遍,初步怀疑是代码问题,导致该对象一直创建,最终导致OOM

代码分析

根据故障分析的对象,判断该对象肯定是一个普遍使用情况,最终对比各个接口,发现了一个JSONSTRING转换使用最频繁,查看JSONUtil.toCompatibleJson底层代码,发现底层代码在做JSON转换的时候有问题。

//对Long型兼容js的json串
    public static final String toCompatibleJson(Object object, String format) {
        SerializeWriter out = new SerializeWriter();
        try {
            //此处必须new一个SerializeConfig,防止修改默认的配置
            JSONSerializer serializer = new JSONSerializer(out, new SerializeConfig());
            serializer.getMapping().put(Long.class, longSerializer);
            if (format != null) {
                serializer.getMapping().put(Date.class, new SimpleDateFormatSerializer(format));
            }
            serializer.write(object);
            return out.toString();
        } finally {
            out.close();
        }
    }

该方法在JSONSerializer对象创建时需要new SerializeConfig(),SerializeConfig 默认会激活 asm,在序列化对象时会为对象生成代理类,然后通过执行代理进行序列化操作,通过这样优化来提高执行性能,但在应用不合理每次新创建 config 的时候就会导致大量生成代码类反而拖慢性能。

修改方案

1,修改了所有log.info通过JSONUtil.toCompatibleJson转换对象或者LIST进行日志打印的地方,因为这一块不涉及逻辑,可以直接发布生产。但是发布完成后,发现类加载跟之前同时期对比,并没有明显变化,由此分析并没有改到关键点。
2,因为没有办法确认问题,只能本地进行测试,针对JSONUtil.toCompatibleJson进行测试,发现并不是所有的对象都有生成代理类,只有class bean会,针对于基础类型,集合,Map都不会生成代理类,就不会造成内存增大。
3,在测试环境针对改方法进行跑批,发现类加载内存有明细增加,修复JSONUtil.toCompatibleJson问题后在跑批,发现类加载超级稳定,基本上维持不变,故问题找到。

结论

此次事故主要原因有以下几个:
1,服务器集群数量减少,因为成本控制,缩小了50%的机器,导致集群内存整体缩小
2,服务器部署减少,因为部署减少,导致服务器处于持续内存增加
3,使用了JSON.toCompatibleJson方法后,默认生成代理类,导致内存溢出
4,在 jdk8 之前这些代理类会充满 Perm 区导致 FullGC,浪费点 CPU 也不会有大问题,但在 JDK8 中,这些类会大量创建直至充满物理机内存,进而导致进程被系统杀掉。

解决方案

将toCompatibleJson方法改成JSON.toJSONString方法,回顾toJSONString源码可以看到,基本上和toCompatibleJson一致,唯独在SerializeConfig对象上不是new而是初始化一次对象

public final static SerializeConfig  globalInstance = new SerializeConfig();
上一篇下一篇

猜你喜欢

热点阅读