进一步分析并彻底解决 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内存泄漏的问题,并提供了解决方案。

相关推荐
GISer_Jing4 小时前
AI自动化工作流:智能驱动未来(升级研究生项目!!!)
人工智能·前端框架·自动化
草捏子4 小时前
Agent Skills:让 AI 一次学会、永远记住的能力扩展方案
人工智能
NocoBase4 小时前
【2.0 教程】第 1 章:认识 NocoBase ,5 分钟跑起来
数据库·人工智能·开源·github·无代码
后端小肥肠4 小时前
OpenClaw实战|从识图到公众号内容自动化,我跑通了完整链路
人工智能·aigc·agent
猿界零零七4 小时前
pip install mxnet 报错解决方案
python·pip·mxnet
Elastic 中国社区官方博客4 小时前
快速 vs. 准确:衡量量化向量搜索的召回率
大数据·人工智能·elasticsearch·搜索引擎·ai·全文检索
qq_381338504 小时前
【技术日报】2026-03-18 AI 领域重磅速递
大数据·人工智能
NocoBase4 小时前
开源项目管理工具选型指南(2026年最新)
人工智能·开源·无代码
feasibility.5 小时前
AI 爬虫高手养成:Openclaw+Scrapling 手动部署 + 采集策略(以Walmart 电商平台为例)
人工智能·爬虫·科技·机器人·agi·openclaw·scrapling
程序员老猫5 小时前
前端菜鸡狂喜!DeepSeek+Gemini,嘴炮出完整博客方案
人工智能