JVM内存分析:Aviator低版本内存泄漏问题分析

article/2025/10/23 23:57:05

目录

1.频繁FullGC告警

2.堆转储操作生成dump文件

3.利用MAT工具分析dump文件

3.1 大对象视图分析内存泄漏原因

3.2 Aviator框架中什么地方用到ThreadLocal?

3.3 fnLocal为什么存在内存泄漏?

3.4 LambdaFunctionBootstrap为什么没有释放?

3.5 老年代内存占用曲线中,为什么内存占用越来越多(FullGC回收的低点逐渐抬高)?

4 解决方案


本文通过实际线上项目中频繁FullGC告警的场景,利用MAT内存分析工具,重点分析Aviator低版本内存泄漏问题的排查过程,并深入分析Aviator框架源码深层次的实质根因,最后结合高版本的修复方案,解决内存泄漏问题;

1.频繁FullGC告警

在实际项目开发过程中,我们会使用Aviator表达式引擎针对配置化的表达式进行求值计算;

项目在线上平稳运行一段时间后,收到一台机器频繁FullGC的告警,观察线上机器老年代使用情况如下:

部署机器的jvm配置参数为: -Xmx4096m -Xms4096m -XX:MaxPermSize=512m -Xmn1512m

堆内存(4G),新生代(1512M),SurvivorRatio=8,所以Eden(1209.6M),

One Survivor(151.2M),老年代(2584M)

也可通过/usr/local/java8/bin/jmap -heap <pid> 命令查看内存分配情况

这里可以看到老年代为2584M

随着项目的持续运行,老年代逐步耗尽,且FullGC无法回收,出现内存泄漏问题,下面对该内存泄漏问题进行具体分析;

附:Aviator执行过程源码详见:Aviator源码:Aviator表达式引擎执行过程源码分析,可以较好的理解后续的引用链分析过程以及涉及的LambdaFunctionBootstrap和LambdaFunction对象含义。

2.堆转储操作生成dump文件

收到机器频繁FullGC告警之后,因为是分布式集群部署,首先禁用该问题机器的流量,规避对线上业务的影响,同时保存机器内存现场;然后通过堆转储命令生成dump文件,方便后续dump内存分析;

堆转储命令为:

/usr/local/java8/bin/jmap -dump:format=b,file=/tmp/heapdump.phrof <pid>

进行堆转储操作之前,也可以直接通过jmap命令查看大对象的具体情况,命令如下(简单的内存问题可以直接通过该命令分析出原因):

usr/local/java8/bin/jmap -histo <pid> | head -n20

3.利用MAT工具分析dump文件

3.1 大对象视图分析内存泄漏原因

首先在大对象分析视图中,可以看到存在3个比较大的业务线程,共占据了45%以上的内存空间,将近1G的内存占用;

经验分析,对于Thread空间占用较大,一般都是因为线程本地变量较大造成的,下面进行具体分析;

跟踪Thread的引用链,可以看到的确是由于ThreadLocal引起的内存泄漏,这里ThreadLocalMap包含3万多个Entry,且每一个Entry的占用近32K的空间;

同时也可以看到Entry的值对象是LambdaFunction,也及系统执行流程中包含了ThreadLocal<LambdaFunction> 对象的使用,因为在业务代码中没有相关逻辑,且LambdaFunction是Aviator框架中定义的类型,排查范围扩展到Aviator框架源码;

注:这里最终的大对象是存放到Env中的业务定义对象(大小近30k),且由于数量比较多,导致最终占用内存较多;

3.2 Aviator框架中什么地方用到ThreadLocal<LambdaFunction>?

项目中应用的aviator版本为5.2.3,具体引用的地方是在LambdaFunctionBootstrap类中,如下:

且类LambdaFunctionBootstrap对该成员变量的使用方法如下:

附源码:

package com.googlecode.aviator.runtime;import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import com.googlecode.aviator.BaseExpression;
import com.googlecode.aviator.Expression;
import com.googlecode.aviator.parser.VariableMeta;
import com.googlecode.aviator.runtime.function.LambdaFunction;
import com.googlecode.aviator.utils.Env;/*** A lambda function creator.** @author dennis**/
public class LambdaFunctionBootstrap {// the generated lambda class nameprivate final String name;// The compiled lambda body expressionprivate final BaseExpression expression;// The method handle to create lambda instance.// private final MethodHandle constructor;// The arguments list.private final List<FunctionParam> params;private final boolean inheritEnv;private final ThreadLocal<LambdaFunction> fnLocal = new ThreadLocal<>();public String getName() {return this.name;}public LambdaFunctionBootstrap(final String name, final Expression expression,final List<FunctionParam> arguments, final boolean inheritEnv) {super();this.name = name;this.expression = (BaseExpression) expression;// this.constructor = constructor;this.params = arguments;this.inheritEnv = inheritEnv;}public Collection<VariableMeta> getClosureOverFullVarNames() {Map<String, VariableMeta> fullNames = this.expression.getFullNameMetas();for (FunctionParam param : this.params) {fullNames.remove(param.getName());}Iterator<Map.Entry<String, VariableMeta>> it = fullNames.entrySet().iterator();while (it.hasNext()) {Map.Entry<String, VariableMeta> fullName = it.next();for (FunctionParam param : this.params) {if (fullName.getKey().startsWith(param.getName() + ".")) {it.remove();break;}}}return fullNames.values();}public Expression getExpression() {return this.expression;}/*** Create a lambda function.** @param env* @return*/public LambdaFunction newInstance(final Env env) {LambdaFunction fn = null;if (this.inheritEnv && (fn = this.fnLocal.get()) != null) {fn.setContext(env);return fn;}// try {fn = new LambdaFunction(this.name, this.params, this.expression, env);fn.setInheritEnv(this.inheritEnv);if (this.inheritEnv) {this.fnLocal.set(fn);}return fn;// final LambdaFunction fn =// (LambdaFunction) this.constructor.invoke(this.params, this.expression, env);// } catch (ExpressionRuntimeException e) {// throw e;// } catch (Throwable t) {// throw new ExpressionRuntimeException("Fail to create lambda instance.", t);// }}
}

3.3 fnLocal为什么存在内存泄漏?

在fnLocal的引用方法中可以看到,方法newInstance中只对fnLocal进行了设置,没有显式进行remove操作(这个是该类的设计理念决定的,fnLocal用作线程内缓存实现方案,在运行期间有效,没有进行显式remove),且因为值对象强引用且fnLocal一直被LambdaFunctionBootstrap对象持有引用,同时LambdaFunctionBootstrap未GC,这样在FullGC时,fnLocal的值对象LambdaFunction所占用内存无法回收;

所以fnLocal设置的线程本地缓存,既没有显式清除,值对象强引用且fnLocal一直被LambdaFunctionBootstrap对象持有引用,同时LambdaFunctionBootstrap未GC,存在内存泄漏风险;当缓存值对象是大对象时,容易导致频繁FullGC,但又回收不掉的情况,和前述的现象一致;

3.4 LambdaFunctionBootstrap为什么没有释放?

LambdaFunctionBootstrap是表达式脚本中lambda类型脚本编译后的类型表示,比如if、while、for语句等,整个脚本编译完成后,LambdaFunctionBootstrap对象存放在编译结果ClassExpression实例对象中,aviator源码中整体的引用链(通过dump内存引用链分析也可以得出)如下:

 对上图中的引用关系具体说明如下:

1)ClassExpression通过成员变量lambdaBootstraps持有了LambdaFunctionBootstrap的引用

2)LambdaFunctionBootstrap通过成员变量持有ThreadLocal的强引用,造成ThreadLocal无法GC回收

3)线程本地变量的值对象为LambdaFunction,且LambdaFunction通过成员变量context持有Env的引用

4)Env对象通过成员变量持有编译结果ClassExpression的引用

由此,上述对象之间存在一个引用环,都是持有的强引用,最后导致所有对象都无法被FullGC回收;

因此,只要表达式脚本中包含了lambda类型的脚本,且使用到了线程本地缓存(inheritEnv为true),就会存在内存泄漏的风险;

3.5 老年代内存占用曲线中,为什么内存占用越来越多(FullGC回收的低点逐渐抬高)?

在3.4节的分析中,我们知道未开启aviator LRU缓存或者开启缓存但未命中缓存的情况下,表达式脚本就会重新编译,生成新的LambdaFunctionBootstrap和LambdaFunction实例对象,随着项目的持续运行,LambdaFunctionBootstrap和LambdaFunction实例对象会越来越多,且都无法回收;

LambdaFunctionBootstrap和LambdaFunction实例数在MAT对象实例视图中也可以看到:

而实际项目中包含的脚本远远达不到这么多;

因此 LambdaFunctionBootstrap和LambdaFunction实例对象越来越多,且都无法回收,导致内存泄漏情况越来越严重,FullGC回收后的低点逐渐抬高。

4 解决方案

 该内存泄漏问题存在于aviator低版本(version<5.3.3)中,最新高版本已经进行了修复,修复改动如下:

这里主要是将ThreadLocal的值对象的强引用改为了软引用,这样在FullGC的时候LambdaFunction就可以被正常回收,本质是因为上述引用环中的结构被打破了,如下:

通过改为软引用,FullGC时,LambdaFunction就可以被正常回收,释放线程本地变量内存占用,内存泄漏问题得到解决; 


http://chatgpt.dhexx.cn/article/C7awDq0q.shtml

相关文章

Aviator 规则引擎介绍

先说结论&#xff1a; 规则简单&#xff1a;qlexpress或者avaitor&#xff1b;规则复杂&#xff1a;drools 最终选择是因为&#xff1a;足够轻量级&#xff0c;社区活跃度较好&#xff0c;最新jar包更新于22年4月 String expression "a河北省&&((b男人||c>3…

aviator实战

aviator规则引擎轻量、高性能可以帮我们解决很多配置规则的问题 官方文档参考&#xff1a;Aviator帮助文档 实战一把aviator&#xff0c;举个栗子 对年龄在40岁以内、职级大于等于3的“宋姓”销售人员发放提成&#xff0c; 提成计算公式“销售量 * 单件提成金额 * 10%” 代码…

aviator java,基于Aviator的规则引擎系统设计

项目里或多或少的都会有一些基于规则判断的代码&#xff0c;而往往这部分逻辑判断代码都写在项目里&#xff0c;改规则就得改代码&#xff0c;所以就很不方便&#xff0c;而且随着规则越多&#xff0c;代码越繁杂。 最近有时间&#xff0c;设计了一套基于Aviator的规则引擎系统…

Aviator源码:Aviator表达式引擎执行过程源码分析

目录 1.if执行脚本示例 2.源码分析 2.1 compile执行过程 2.1.1 CodeGenerator 2.1.2 ExpressionParser 2.1.3 if脚本ASM反编译结果 2.2 compiledExpression.execute执行过程 3.总结概述 由于Aviator支持的脚本语法较多&#xff0c;下面通过项目中使用较多的if语句来对a…

Aviator使用

“初步了解和使用Aviator” 1.Aviator简介 Aviator 是一个高性能&#xff0c;轻量级的java语言实现的表达式求值引擎&#xff0c;主要用于各种表达式的动态求值。 官方文档 github地址 支持数字、字符串、正则表达式、布尔值、正则表达式等基本类型&#xff0c;完整支持所有…

java aviator_Aviator 表达式求值引擎开源框架

简介 Aviator是一个高性能、轻量级的java语言实现的表达式求值引擎&#xff0c;主要用于各种表达式的动态求值。现在已经有很多开源可用的java表达式求值引擎&#xff0c;为什么还需要Avaitor呢&#xff1f; Aviator的设计目标是轻量级和高性能&#xff0c;相比于Groovy、JRuby…

java aviator_Aviator——让表达式飞起来

《飞行大亨》是我很喜欢的一部电影&#xff0c;不过这里我想介绍的是一个叫Aviator的开源的Java表达式求值器。 一、轮子的必要性 表达式的求值上&#xff0c;java的选择非常多&#xff0c;强大的如Groovy、JRuby&#xff0c;N年没维护的beanshell&#xff0c;包括javaeye上朋友…

Aviator

Aviator 简介 Aviator是一个高性能、轻量级的java语言实现的表达式求值引擎&#xff0c;主要用于各种表达式的动态求值。现在已经有很多开源可用的java表达式求值引擎&#xff0c;为什么还需要Avaitor呢&#xff1f; Aviator的设计目标是轻量级和高性能 &#xff0c;相比于Groo…

Aviator介绍

Aviator简介 Aviator是一个高性能、轻量级的 java 语言实现的表达式求值引擎, 主要用于各种表达式的动态求值。现在已经有很多开源可用的 java 表达式求值引擎,为什么还需要 Avaitor 呢? Aviator的设计目标是轻量级和高性能,相比于Groovy、JRuby的笨重, Aviator非常小, 加上…

西电计算机学院名誉院长,杨孟飞院士受聘为西电计科院名誉院长及讲席教授

西电新闻网讯(通讯员 陈龙)12月21日上午&#xff0c;西安电子科技大学计算机科学与技术学院名誉院长杨孟飞院士“华山学者”讲席教授及战略咨询委员会委员聘任仪式在北校区主楼Ⅱ区319会议室举行&#xff0c;校长杨宗凯、副校长王泉参加仪式。仪式由计科院执行院长崔江涛主持。…

北航计算机学院新闻,澳门理工学院代表团访问我校计算机学院

北航新闻网1月21日电(通讯员 盛浩)2019年1月16日&#xff0c;澳门理工学院副院长李雁莲教授、孙毓奇教授等一行三人到北航计算机学院就双方合作等事项进行交流。 座谈会由先进计算机应用技术教育部工程研究中心熊璋教授主持&#xff0c;计算机学院副院长胡春明副教授、中心李超…

【华人学者风采】陈晓峰 西安电子科技大学

【华人学者风采】陈晓峰&#xff0c;西安电子科技大学网络与信息安全学院副院长。研究方向包括公钥密码学、金融密码学、云计算安全、数据安全、区块链技术及应用、人工智能安全。曾获荣誉&#xff1a; 国家万人计划科技创新领军人才&#xff0c;教育部“青年长江学者” &#…

港科资讯 | 倪明选校长等出席江门“双碳”实验室揭牌暨项目签约仪式

12月7日&#xff0c;由香港科技大学&#xff08;广州&#xff09;&#xff08;筹&#xff09;和江门市政府发起共建的江门“双碳”实验室举行了揭牌暨项目签约仪式&#xff0c;江门双碳实验室正式启动成立。香港科技大学&#xff08;广州&#xff09;&#xff08;筹&#xff09…

莫队算法(普通莫队、带修莫队、树上莫队、不删除莫队)学习笔记【理解+套路/核心代码+例题及题解】

一、理解 我的理解就是巧妙的暴力&#xff0c;利用双指针以及分块思想&#xff0c;巧妙的移动双指针&#xff0c;时间复杂度可以达到O(NlogN)。 强推博客&#xff1a;写的又好又全。链接 二、套路 1、普通莫队 【1】核心代码 bool cmp(node a,node b){return belong[a.l]…

香港理工大学计算机系石杰明老师组招收全奖博士生、博士后

来源&#xff1a;AI求职 香港理工大学 香港理工大学位于中国香港特别行政区&#xff0c;QS 最新排名世界 66 位。计算机系&#xff08;Department of Computing&#xff09;USNews CS 排名 36&#xff0c;泰晤士 CS 排名 79。 石杰明博士课题组成员包括有 985/211 顶尖高校背景…

【调剂】华侨大学计算机学院计算机视觉与模式识别实验室钟必能课题组研究生招生...

点击文末的阅读原文或者公众号界面左下角的调剂信息或者公众号回复“调剂”是计算机/软件等专业的所有调剂信息集合&#xff0c;会一直更新的。 课题组主页&#xff1a;https://cst.hqu.edu.cn/info/1109/2001.htmLinkedin: https://www.linkedin.com/in/bineng-zhong-71a36674…

莫队算法思想

目录 莫队算法普通莫队方法&#xff1a;主要代码结构&#xff1a;例题&#xff1a;小B的询问例题&#xff1a;小Z的袜子奇偶化排序 带修改的莫队小结&#xff1a; 莫队算法 莫队算法是由前国家队莫涛提出的一种算法&#xff0c;主要应用在一类离线区间查询的问题中&#xff0c…

【华人学者风采】冯佳时 新加坡国立大学

【华人学者风采】冯佳时&#xff0c;新加坡国立大学ECE系助理教授。本科毕业于中国科学技术大学&#xff0c;硕士毕业于中国科学院自动化研究所&#xff0c;博士毕业于新加坡国立大学。研究兴趣包括大污染数据分析&#xff0c;在线和分布式鲁棒性学习及其在对象识别中的应用。 …

港科夜闻|央视网专访香港科大(广州)(筹)校长倪明选教授,谈香港科技大学在科研及知识转移方面成就...

关注并星标 每周阅读港科夜闻 建立新视野 开启新思维 1、央视网专访香港科大(广州)(筹)校长倪明选教授&#xff0c;谈香港科技大学在科研及知识转移方面成就。香港科技大学(广州)(筹)校长倪明选教授接受央视网专访&#xff0c;谈香港科技大学在科研及知识转移方面取得的成就&am…

独家对话许诗军:数字化转型,最基本的是不去拒绝 |数字价值观察室(下)...

关注ITValue&#xff0c;看企业级最新鲜、最价值报道&#xff01; ▎本文摘自《云栖战略参考》&#xff0c;这本刊物由阿里云与钛媒体联合策划。目的是为了把各个行业先行者的技术探索、业务实践呈现出来&#xff0c;与思考同样问题的“数字先行者”共同探讨、碰撞&#xff0c;…