进一步分析并彻底解决 Flink container exit 143 问题

你好,我是 shengjk1,多年大厂经验,努力构建 通俗易懂的、好玩的编程语言教程。 欢迎关注!你会有如下收益:

  1. 了解大厂经验
  2. 拥有和大厂相匹配的技术等 希望看什么,评论或者私信告诉我!

一、前言

上一篇文章 我们通过 增大 metaspace 的内存,让程序跑了起来,不影响线上使用。今天呢,就彻底解决 metaspace 不断增长的问题

二、 metaspace

2.1 metaspace 是什么

这是 java 的内存模型,从 1.8 开始metaspace 替代方法区,用于存储类定义、方法数据、字段数据等元数据,并且呢它是从 是从本机内存中分配的,大小不固定。

也就是说如果不断的创建类等,metaspace 总会被撑爆的

2.2 metaspace 如何设置

对于 java 来说

text 复制代码
-   `-XX:MetaspaceSize`

这个参数是初始化的Metaspace大小,该值越大触发Metaspace GC的时机就越晚。随着GC的到来,虚拟机会根据实际情况调控Metaspace的大小,可能增加上线也可能降低。在默认情况下,这个值大小根据不同的平台在`12M到20M`浮动。使用`java -XX:+PrintFlagsInitial`命令查看本机的初始化参数,`-XX:Metaspacesize`为`21810376B`(大约20.8M)。

-   `-XX:MaxMetaspaceSize`  
    这个参数用于限制Metaspace增长的上限,防止因为某些情况导致Metaspace无限的使用本地内存,影响到其他程序。在本机上该参数的默认值为4294967295B(大约4096MB)。
-   `-XX:MinMetaspaceFreeRatio`  
    当进行过Metaspace GC之后,会计算当前Metaspace的空闲空间比,如果空闲比小于这个参数,那么虚拟机将增长Metaspace的大小。在本机该参数的默认值为40,也就是40%。设置该参数可以控制Metaspace的增长的速度,太小的值会导致Metaspace增长的缓慢,Metaspace的使用逐渐趋于饱和,可能会影响之后类的加载。而太大的值会导致Metaspace增长的过快,浪费内存。
-   `-XX:MaxMetasaceFreeRatio`  
    当进行过Metaspace GC之后, 会计算当前Metaspace的空闲空间比,如果空闲比大于这个参数,那么虚拟机会释放Metaspace的部分空间。在本机该参数的默认值为70,也就是70%。
-   `-XX:MaxMetaspaceExpansion`  
    Metaspace增长时的最大幅度。在本机上该参数的默认值为5452592B(大约为5MB)。
-   `-XX:MinMetaspaceExpansion`  
    Metaspace增长时的最小幅度。在本机上该参数的默认值为340784B(大约330KB为)。

对于 Flink 来说

txt 复制代码
taskmanager.memory.jvm-metaspace.size=1024mb

三、结合 GC 日志分析问题

3.1 问题分析

text 复制代码
2024-05-08T11:11:35.075+0800: 22.379: [GC (Metadata GC Threshold) [PSYoungGen: 428583K->21165K(2160128K)] 451157K->43747K(7097344K), 0.0344272 secs] [Times: user=0.08 sys=0.02, real=0.04 secs] 

2024-05-08T11:11:35.109+0800: 22.413: [Full GC (Metadata GC Threshold) [PSYoungGen: 21165K->0K(2160128K)] [ParOldGen: 22581K->35384K(4937216K)] 43747K->35384K(7097344K), [Metaspace: 34235K->34235K(1079296K)], 0.2253439 secs] [Times: user=0.54 sys=0.03, real=0.22 secs]

发现 Full GC 前后 Metaspace 的内存大小并没有变化,很是奇怪, 为了弄清楚为啥,我增加了如下两个JVM启动参数来观察类的加载、卸载信息:

ruby 复制代码
-XX:TraceClassLoading -XX:TraceClassUnloading

加了这两个参数后,系统跑了一段时间,从GC日志中发现大量如下的日志:

另外在 aviatorscript 也发现了对应的issues

源码分析

3.3 源码解析

查看代码我们可以看到Aviator提供了两个调用接口:

typescript 复制代码
public static Object execute(String expression, Map<String, Object> env, boolean cached) {
	return getInstance().execute(expression, env, cached);
}
public static Object execute(String expression, Map<String, Object> env) {
	return execute(expression, env, false);
}

深入源码:

kotlin 复制代码
public Expression compile(final String expression, final boolean cached) {
        if (expression != null && expression.trim().length() != 0) {
            if (cached) {
                FutureTask<Expression> task = (FutureTask)this.cacheExpressions.get(expression);
                if (task != null) {
                    return this.getCompiledExpression(expression, task);
                } else {
                    task = new FutureTask(new Callable<Expression>() {
                        public Expression call() throws Exception {
                            return AviatorEvaluatorInstance.this.innerCompile(expression, cached);
                        }
                    });
                    FutureTask<Expression> existedTask = (FutureTask)this.cacheExpressions.putIfAbsent(expression, task);
                    if (existedTask == null) {
                        existedTask = task;
                        task.run();
                    }

                    return this.getCompiledExpression(expression, existedTask);
                }
            } else {
                return this.innerCompile(expression, cached);
            }
        } else {
            throw new CompileExpressionErrorException("Blank expression");
        }
    }

可以发现核心方法是innerCompile方法。继续深入找到cached参数最底层的使用:

typescript 复制代码
public AviatorClassLoader getAviatorClassLoader(boolean cached) {
        return cached ? this.aviatorClassLoader : new AviatorClassLoader(Thread.currentThread().getContextClassLoader());
    }

综上,我们发现

  • cached参数为true时,会优先从缓存中获取编译好的表达式对象。同时使用编译表达式使用的类加载器也是同一个。
  • 而cached为false时,每次执行表达式都会去编译表达式,且每次编译使用的是一个全新的类加载器。这是导致元数据区加载太多"一次性"类的元凶。

四、问题解决

最终,在调用参数中,将cached设置为true,成功解决Full GC (Metadata GC Threshold)问题

五、总结

本文介绍了Java中Metaspace的相关知识和参数设置,同时提供了解决Metaspace内存泄漏的方案。同时,通过分析GC日志,发现了使用Aviator表达式引擎时可能导致Metaspace内存泄漏的问题,并提供了解决方案。

相关推荐
Q_19284999064 分钟前
基于Spring Boot的摄影器材租赁回收系统
java·spring boot·后端
吃个糖糖6 分钟前
35 Opencv 亚像素角点检测
人工智能·opencv·计算机视觉
良许Linux9 分钟前
0.96寸OLED显示屏详解
linux·服务器·后端·互联网
求知若饥21 分钟前
NestJS 项目实战-权限管理系统开发(六)
后端·node.js·nestjs
qq_5290252923 分钟前
Torch.gather
python·深度学习·机器学习
数据小爬虫@24 分钟前
如何高效利用Python爬虫按关键字搜索苏宁商品
开发语言·爬虫·python
Cachel wood1 小时前
python round四舍五入和decimal库精确四舍五入
java·linux·前端·数据库·vue.js·python·前端框架
IT古董1 小时前
【漫话机器学习系列】017.大O算法(Big-O Notation)
人工智能·机器学习
凯哥是个大帅比1 小时前
人工智能ACA(五)--深度学习基础
人工智能·深度学习
終不似少年遊*1 小时前
pyecharts
python·信息可视化·数据分析·学习笔记·pyecharts·使用技巧