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

相关推荐
sunfove20 小时前
空间几何的基石:直角、柱、球坐标系的原理与转换详解
人工智能·python·机器学习
<-->20 小时前
pytorch vs ray
人工智能·pytorch·python
qq_124987075320 小时前
基于Spring Boot的“味蕾探索”线上零食购物平台的设计与实现(源码+论文+部署+安装)
java·前端·数据库·spring boot·后端·小程序
知乎的哥廷根数学学派20 小时前
基于多尺度特征提取和注意力自适应动态路由胶囊网络的工业轴承故障诊断算法(Pytorch)
开发语言·网络·人工智能·pytorch·python·算法·机器学习
Yuer202520 小时前
低熵回答倾向:语言模型中的一种系统稳定态
人工智能·机器学习·语言模型·ai安全·edca os
七夜zippoe20 小时前
缓存策略:从本地到分布式架构设计与Python实战
分布式·python·缓存·lfu·lru
曲幽20 小时前
重构FastAPI生产部署:用异步网关与无服务器计算应对高并发
python·serverless·fastapi·web·async·httpx·await·asyncio
yuzhiboyouye20 小时前
c/p比结合VIX值,最早的信号
人工智能
Byron Loong20 小时前
【机器视觉】GTX5050到GTX5090算力比较
人工智能
开心就好202520 小时前
Python爬虫基础:HTTP和HTTPS协议的请求与响应过程详解
后端