记一次真实的线上OOM

背景

晚上快下班,前端告诉我有一个接口迟迟没有响应,可这个项目很久没提过新东西了,更不要说上线了。

排查思路

于是先去登到机器上top,top -Hp一下看下CPU情况,好像并没有什么异常。 然后再free -h看内存,发现确实服务器内存剩余空间不足500MB,那么大概率是内存泄露的问题。 那么直接看下jvm gc情况:

shell 复制代码
jstat -gcutil 38714 
S0    S1    E      O     M    CCS   YGC  YGCT   FGC     FGCT       GCT 
0.00 0.00 99.99 100.00 88.26 84.01 4494 48.648 127745 30434.755 30483.404
  • 那么问题显而易见
    • Old 区 100%,Eden 区 99.99%老年代和Eden区全部被占满,对象无法分配,也无法回收。
    • FGC = 127745,FGCT = 30434.755 秒Full GC 发生了 12 万次 ,累计耗时 8.45 小时
    • S0/S1 为 0 :因为 Old 区满了,对象从 Young 区晋升不上去,Survivor 区也无法正常工作

确实是很严重的线上问题了,那么立刻通过 jmap 命令 dump内存快照。

shell 复制代码
jmap -dump:format=b,file=heap.hprof 38714

然后重启服务,保证线上服务是可用的。 本地通过MAT (Eclipse Memory Analyzer) 分析dump文件 打开dump文件可以发现:

可疑问题对象占了1.5GB堆内存

Leak Suspects

堆内 jcifs.context.BaseContext 对象实例有19,575个,并且占了87.18%的内存,那这一定是不合理的。 再看 Common Path To the Accumulation Point

确实问题点都出现在这里。

那么再去看业务代码,看看哪里导致了OOM。

java 复制代码
public static List<TreeNode> getPackage() {
        java.util.Properties props = new java.util.Properties();
        props.put("jcifs.smb.client.minVersion", "SMB202");
        props.put("jcifs.smb.client.maxVersion", "SMB311");
        props.put("jcifs.smb.client.disableSMB1", "true");
        props.put("jcifs.smb.client.encrypt", "true");

        CIFSContext baseContext = new BaseContext(new PropertyConfiguration(props));
        NtlmPasswordAuthentication authentication = new NtlmPasswordAuthentication(baseContext, packageIP, packageUser, packagePsw);
        CIFSContext authContext = baseContext.withCredentials(authentication);
        SmbFile remoteFile = new SmbFile(packagePath, authContext);
        ...
}

每次调用此方法都会创建一个BaseContext对象,并且最终没有做close,那么有可能是这里出现了问题。刚好注意到jcifs库有这样一个issue

大概意思是SmbFileConnection类的构造器会创建一个jcifs.context.BaseContext对象,jcifs.context.BaseContext对象创建时会调用其超类的AbstractCIFSContext的构造方法,AbstractCIFSContext的构造方法中会为jcifs.context.BaseContext对象注册一个shutdown hookSmbFileConnection对象使用结束,会被垃圾回收器回收掉,但是jcifs.context.BaseContext对象还会存在一个shutdown hook的引用链导致不能被回收。

issue中的问题在jcifs 3.9.0版本仍然存在,我们项目中使用的jcifs版本是2.1.7,为了保险,再看一下2.1.7版本的jcifs怎么实现的。

java 复制代码
    public abstract class AbstractCIFSContext extends Thread implements CIFSContext {
        private static final Logger log = LoggerFactory.getLogger(AbstractCIFSContext.class);
        private boolean closed;

        public AbstractCIFSContext() {
            Runtime.getRuntime().addShutdownHook(this);
        }
    ...
    }

确实还是有此问题的,那么就可以着手改代码了。

解决问题

其实显示调用close()方法是可以解决此问题的,贴上源码:

java 复制代码
    public boolean close() throws CIFSException {
        if (!this.closed) {
            Runtime.getRuntime().removeShutdownHook(this);
        }

        return false;
    }

但是为了防止频繁的创建销毁对象,我选择把此对象做成单例的

java 复制代码
private static CIFSContext getBaseContext() {
    if (baseContext == null) {
        synchronized (PackageBranchUtil.class) {
            java.util.Properties props = new java.util.Properties();
            props.put("jcifs.smb.client.minVersion", "SMB202");
            props.put("jcifs.smb.client.maxVersion", "SMB311");
            props.put("jcifs.smb.client.disableSMB1", "true");
            props.put("jcifs.smb.client.encrypt", "true");
            try {
                baseContext = new BaseContext(new PropertyConfiguration(props));
            } catch (jcifs.CIFSException e) {
                log.error("Failed to initialize CIFS context: {}", e.getMessage());
                throw new RuntimeException("Failed to initialize CIFS context", e);
            }
        }
    }
    return baseContext;
}

验证

先压测一把

再次dump,查看堆区内存

可以看到此接口不会导致jcifs.context.BaseContext对象泛滥的情况了,搞定!

如需转载,请私信联系博客作者

相关推荐
SunnyDays10112 小时前
如何在Java中将Word文档转换为图像(JPEG、PNG或SVG)
java
Lumos_7772 小时前
Linux -- 线程
java·jvm·算法
知兀2 小时前
【MybatisPlus】后端用枚举类,数据库用tinyint,存在枚举类型转换
java
StockTV2 小时前
印度股票实时数据 NSE和BSE的实时行情、K 线及指数数据
java·开发语言·spring boot·python
User_芊芊君子2 小时前
【OpenAI 把 AI 玩明白了】:自主推理 + 动态知识图谱,这 4 个技术突破要颠覆行业
java·人工智能·知识图谱
c++之路3 小时前
C++20概述
java·开发语言·c++20
Championship.23.243 小时前
Linux Top 命令族深度解析与实战指南
java·linux·服务器·top·linux调试
橘子海全栈攻城狮3 小时前
【最新源码】养老院系统管理A013
java·spring boot·后端·web安全·微信小程序
逻辑驱动的ken3 小时前
Java高频面试考点18
java·开发语言·数据库·算法·面试·职场和发展·哈希算法