现象
在对catboost模型(大小1g)做切换测试的时候,出现Java进程异常退出的情况。
JVM监控:
内存稳定,且没有出现FullGC,没有发生oom 
                
MDC监控:
cpu使用率接近70%,内存使用率接近100% 
                
怀疑使用了堆外内存,导致容器内存打满。
排查加载catboost模型代码,发现catboost提供sdk中loadModel方法调用了JNI,推测在native方法中申请了内存。
| 1 | public static CatBoostModel loadModel(InputStream in) throws CatBoostError, IOException { | 
调整堆外内存大小调整机器配置
8c12g -> 8c16g
JVM堆内存参数配置不变(即堆外内存增大4g)
-Xms9632M
-Xmx9632M
JVM监控: 
                
MDC监控: 
                
模型切换次数由原来的1、2次,提升到7次才出现Java进程异常退出的情况,基本可以确定堆外内存搞的鬼了。
主动释放模型资源
在CatBoostModel对象被回收的时候,finalize方法中会自己调用dispose方法,释放资源1
2
3
4
5
6
7
8
9
10
11
12
13
14protected void finalize() throws Throwable {
    try {
        this.dispose();
    } finally {
        super.finalize();
    }
}
private synchronized void dispose() throws CatBoostError {
    if (this.handle != 0L) {
        implLibrary.catBoostFreeModel(this.handle);
        this.handle = 0L;
    }
}
不过我们还是在加载新模型后,尝试主动调用释放旧模型,即增加oldModel.close()代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19public void convert(String modelKey, InputStream inputStream) {
    try {
        CatBoostModel catBoostModel = CatBoostModel.loadModel(inputStream);
        CatBoostModel oldModel = modelMap.get(modelKey);
        modelMap.put(modelKey, catBoostModel);
        if(Objects.nonNull(oldModel)){
            log.info("释放catboost资源");
            oldModel.close();
        }
        log.info("CatBoost init model :{} success!", modelKey);
    } catch (Exception e) {
        log.error("CatBoost init model :{} fail! exception :", modelKey, e);
        throw new BusinessException("CatBoost模型加载失败");
    }
}
public void close() throws CatBoostError {
    this.dispose();
}
JVM监控 
                
MDC监控 
                
还是一样存在内存泄漏的问题。
NMT定位内存区域
可以展示堆内内存、Code区域或者使用unsafe.allocateMemory和DirectByteBuffer申请的堆外内存
JVM启动参数增加:-XX:NativeMemoryTracking=detail
查看命令:jcmd pid VM.native_memory detail
| 1 | [admin@host-11-68-72-16 ~]$ jcmd 217 VM.native_memory detail | 
可以发现Native Memory Tracking中,只分配了6m的空间,我们更加相信是Native Code(C代码)申请的堆外内存导致的问题。
系统层面的工具定位堆外内存
pmap
显示进程的地址空间的相关信息
切换前 
                
切换后 
                
对比切换前后进程分配的内存空间,可以看到每次loadModel会增加700m的内存,而且旧模型申请的空间也没有释放。
strace
我们可以用它来监控用户空间进程和内核的交互。如对应用程序的系统调用、信号传递与进程状态变更等进行跟踪与分析,以达到解决问题的目的。
 
                通过strace监控,切换模型过程中申请内存的命令,可以看到申请了差不多700m内存,可以与上面pmap对应上。
/proc/pid/smaps
我们通过pmap已经发现了可疑的内存区间,在/proc/pid/smaps可以找到分配内存块的起始地址和结束地址
 
                gdb
通过gdb对可疑内存区间进行dump
 
                编码可视化其内容,进行分析,可以看到是模型相关的
 
                 
                 
                