一次线上jvm问题排查的经历

2 篇文章 0 订阅

背景:

通过钉钉和短信提醒得知,有几台阿里云的服务器内存超过阈值触发了报警,当时因为怕影响接口的访问,于是重启了出现问题的容器,通过监控大屏观察,当时并没有任何一个容器发生OOM。

1.使用命令进行排查:

1.1 堆内存排查

过了几天之后开始排查问题,由于没有发生oom,只能一步一步排查,先排查一下堆内存, 进入容器内部ps -ef 直接可以看下运行配置:

image.png

说明有堆内存设置,于是顺手看了一下堆内存中的对象,线上使用的是openjdk8,有些命令是没有的,这里使用了jmap -histo <pid> | head -10 命令看了一下占用堆内存前10的对象:

image.png

可以看出,堆内存最多也就占2-3g左右(此处有伏笔,当时没想着去看元空间的情况,如果当时去看了一下元空间gc情况,到这里应该就知道原因了)

1.2堆外内存排查

因为之前就有引用了第三方jar包导致OOM的,排查后发现是无限创建了线程,所以先查看了一下线程数是否正常,直接使用命令cat /proc/pid/status查看linux的进程转储文件:

image.png

查看了一下jvm参数配置,发现没有配置线程栈大小,说明每个线程的线程栈是默认的1M,那么这里也是正常的。 剩下其他几个内存区域排查,当时最先想的是用jdk自带的NativeMemoryTracking,因为arthas在容器内部署是需要改造的,一时半会可能无法立即在线上使用,但是经过尝试 配置了NativeMemoryTracking,发现线上使用的openjdk没有这个功能,这个就没办法了,只能去使用arthas了

2.使用arthas进行排查:

2.1 direct 区域

过了一段时间后,在线上部署了arthas,因为服务中有使用了dubbo,怀疑有可能是堆外内存泄漏,首先使用memory命令看了一下direct区域的大小,发现是正常的:

image.png

2.2 元空间

当时还是没仔细元空间这块区域,但是在查看classloader时发现了异常:

Pasted Graphic 2.png

这里居然加载了8万多个类,元空间直接占了2-3g,还是挺恐怖的(由于服务还没运行多久,时间长了这种内存泄漏肯定会导致内存报警和OOM),到这肯定了是fastjson的问题了。但是arthas只能一次导出所有类,在线上执行肯定有风险。于是这里先把这个服务切到其他集群了,留了占用内存最大的两个容器进行加载类的导出:

image.png

这里发现很多类都是重复加载,根据关键词应该是fastjson内部维通过AsmClassLoader生成class

3 问题解决:

3.1 问题分析

首先笔者就去看了一下项目的fastjson版本和github上fastjson的issue,没有找到满意的答案,于是就开始看源码分析,线上主要项目版本的都是1.2.83版本的,先找到了是在哪里创建的这些类(直接通过在arthas中看到的关键词查询):

image.png

这个反序列化器工厂应该就是创建这个class的地方,根据源码可以看出这里只是计了个数,并没有找到有去做缓存的地方(每次都会去loadclass导致在元空间生成大量类元信息):

image.png

这里使用的classloader是fastjson自定义的AsmClassloader,AsmClassloader倒是有缓存的,但是缓存的居然是写死的几个类!?

image.png

image.png

到这里其实就明白为什么fastjson会创建那么多重复的class

3.2 问题解决

首先想到的办法是升级版本或者直接更换为spirng使用的jackson,但是后来这两种方法都被笔者否决了。

1.为什么不升级fastjson版本?

fastjson之前风评一直不是太好,各种漏洞,没有jackson来的稳定,就算是新出的fastjson2都一堆问题

2.为什么不换成jackson?

不得不承认,fastjson虽然称为bugson,但是确实用法比较简单,项目使用fastjson时间较长了,有些代码耦合了fastjson相关功能,换成jackson比较麻烦

3.能不能直接把asm关了?

答案是可以的,这里笔者也是想了很久才走到这一步,因为一开始怕关了asm是有风险的,但是不得不走这一步。需要找到fastjson是否有这样的配置,首先找到其中一个配置类,搜索一下asm相关内容:

image.png

好巧不巧第一个就是,这里给了一个setter方法可以对是否开启asm进行设置。 设置如下关闭asm:

public static void main(String[] args) {
    ParserConfig.getGlobalInstance().setAsmEnable(false);
    SerializeConfig.getGlobalInstance().setAsmEnable(false);
    SpringApplication springApplication = new SpringApplication(AppStarter.class);
    springApplication.setAllowBeanDefinitionOverriding(true);
    springApplication.run(args);
}

笔者这里把配置加在启动类里了,至此问题解决。这里验证一个笔者非常关心的问题,关闭asm后,fastjson应该会走反射,那么性能真的会如fastjson介绍里下降很多嘛?(测试结果仅供参考)

public static void main(String[] args) {
    //ParserConfig.getGlobalInstance().setAsmEnable(false);
    //SerializeConfig.getGlobalInstance().setAsmEnable(false);
    BaseModel baseModel = new BaseModel();
    baseModel.setCreationdate(new Date());
    baseModel.setIsactive("Y");
    baseModel.setOwnerid(888L);
    baseModel.setModifiername("asdasdasd");
    long start = System.currentTimeMillis();
    //System.out.println("开始:" + start);
    for (int i = 0; i < 1000; i++) {
        JSONObject jsonObject = (JSONObject) JSONObject.toJSON(baseModel);
        String json = jsonObject.toJSONString();
    }
    long end = System.currentTimeMillis();
    System.out.println("结束:" + (end - start));
}

循环了1000次发现不开启asm反而快(默认开启),大概40ms-50ms之间。开启了asm反而需要130-150ms之间,这里没有继续仔细去研究了,总之不降低太多是可以接受的。 最后回归了一下,目前线上运行了几周后,没有出现过报警:

image.png

说明正常了

总结:

本文先尝试通过命令去排查堆内存、线程是否存在问题,然后使用arthas排查了direct和元空间区域是否有问题,找到是fastjson的问题后,通过分析部分源码找到解决方案。最后,奉劝各位读者选择中间件的时候还是得深入考察一下,否则后期改造相当困难。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值