FastJson竟然会导致内存泄露?你遇到过吗?

FastJson是一款性能优异的java序列化和反序列框架,广泛应用于日常开发工作中,也许正是因为作者在设计这款框架时,比较注重性能方面的考量,在框架安全性,空间占用等方面做了一些牺牲。

很不幸小编前两天就遇到了一个使用FastJson导致内容泄漏的问题。下面是小编从发现内存泄漏问题,到问题排查,再到问题修复的整个过程的记录,当各位读者遇到类似问题后,能有所启发。

某天线上告警,系统jvm堆内存使用率过高,后台登录机器发现jvm 比较gc频繁,而且gc后,对内存占用并没有明显减少,初步怀疑出现了"内存泄漏"。

于是,使用jmap命令查看了一下当前堆内存的信息:

jamp -histo <pid> | head -n 20

结果如下图

在这里插入图片描述

经过多次GC后,上图中的主要对象的数量不降反增,说明系统的确存在内存泄漏问题,而且是FastJson内部的对象。

到这里,第一个问题出现了:上面的FieldInfo,DefaultFieldDeserializer,JavaBeanDeserializer 这些类是干啥的呢?

fastJson反序列化的流程

要了解 FieldInfo,DefaultFieldDeserializer,JavaBeanDeserializer 类的作用,我们需要先了解一下FastJson进行反序列化的流程。

FastJson在将json字符串反序列化成java对象的过程中,会为这个java类生成一个反序列化器,也就是 JavaBeanDeserializer,

同时FastJson还会给这个java类的每个字段都会被封装成一个FeildInfo对象,并且为每个字段也会生成一个 针对字段的反序列化器 DefaultFieldDeserializer

当对一个java类 进行反序列化时,先用字段反序列化器,反序列化出每个字段,然后再用 JavaBeanDeserializer 反序列化出对象。

上面涉及到的FastJson内部类的关系可以参考下图:

在这里插入图片描述

简单来说,就是在反序列化时,一个java类型会对应一个 JavaBeanDeserializer 和 多个 DefaultFieldDeserializer,而且fastJson内部会使用一个类似Map的KV结构 IdentityHashMap 将 类型和 JavaBeanDeserializer进行缓存,防止每次使用时都需要创建,来提升性能。

为什么会产生内存泄露

这一切都看似很合理,但是为什么会出现上图中产生的内存泄漏呢?

在上图中 堆中存在大量的 JavaBeanDeserializer 对象,按照上文讲到的 JavaBeanDeserializer 和类型的对应关系,那么业务系统中 也就会有对应数量的 java类,但是业务系统中其实并没有这么多java类型。

到这里,第二个问题出现了:IdentityHashMap的缓存没有生效吗?

这个时候,我使用了 jmap dump命令将此时内存空间进行了 dump。

jmap -dump:live,format=b,file=dump.bin <pid>

然后使用mat工具分析了一个这么多 JavaBeanDeserializer 对应的java类型都是什么?
在这里插入图片描述

经过分析发现了问题,大量的 JavaBeanDeserializer 对应的 java class 都是一样的。

到这里我们基本上可以得出结论:由于中存在某种问题,产生了java类型一样,但是保存到 IdentityHashMapkey不同,导致 IdentityHashMap 认为他们是不同的数据型,导致大量 相同的 JavaBeanDeserializer 保存到了 IdentityHashMap 中,进而也会存在更多的 DefaultFieldDeserializer ,因为这些 JavaBeanDeserializer 一直被 IdentityHashMap 引用,所以无法被gc回收,也就产生了 内存泄漏。

什么情况下产生内存泄露

知道了问题产生的原因,那接下来就要看下业务系统到底存在什么bug,才会产生很多类型相同但对 IdentityHashMap 而言 Key 却不同的问题呢?

在排查业务系统的bug之前,还要确认下 IdentityHashMap 判断两个Key相同的标准是什么?具体可以看一下代码

public boolean put(K key, V value) {final int hash = System.identityHashCode(key);final int bucket = hash & indexMask;for (Entry<K, V> entry = buckets[bucket]; entry != null; entry = entry.next) {if (key == entry.key) { // 使用 == 进行相同判断entry.value = value;return true;}}Entry<K, V> entry = new Entry<K, V>(key, value, hash, buckets[bucket]);buckets[bucket] = entry;  return false;
}

看以上代码可以发现 IdentityHashMap 判断两个key是否相同的依据是”==“,条件十分苛刻。

接下来就要看一下,业务系统中在进行反序列化时,使用的具体类型是什么,是不是么有满足 上面判断相同的条件呢?

下面是业务系统的伪代码:

    private static void des2(String json) {ParameterizedTypeImpl type = new ParameterizedTypeImpl(new Type[]{SomeInfo.class}, null, CommonVO.class);CommonVO<SomeInfo> tmpResult =  JSON.parseObject(json, type);System.out.println(tmpResult);}

果然有问题,每次进行序列化时,都创建了一个新的数据类型,这也就导致了IdentityHashMap缓存失效,罪魁祸首在这里。

如何解决

知道了问题,修改起来就很简单了:将数据类型作为一个对象属性,或者一个类属性,防止每次调用desc2时,都创建一个新的类型,修改后内存泄漏问题,也就迎刃而解了。

不过除了以上解决方案,FastJson也提供了一个解决方案,使用 TypeRefrence,及时每次序列化化都创建一个 对应的对象也不会产生内存泄漏的问题,为什么 TypeRefrence 不会出现问题呢?
源码之下无秘密,以下TypeRefreen的代码

protected TypeReference(){Type superClass = getClass().getGenericSuperclass();Type type = ((ParameterizedType) superClass).getActualTypeArguments()[0];Type cachedType = classTypeCache.get(type);if (cachedType == null) {classTypeCache.putIfAbsent(type, type);cachedType = classTypeCache.get(type);}this.type = cachedType;}

这里使用了一个 ConcurrentMap 对类型进行缓存,我们知道 ConcurrentMap 判断key相同的依据是 key的hashcode是否一样, 而这里的 ParameterizedType对hashcode和equal方法进行进行了重写:

@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;ParameterizedTypeImpl that = (ParameterizedTypeImpl) o;// Probably incorrect - comparing Object[] arrays with Arrays.equalsif (!Arrays.equals(actualTypeArguments, that.actualTypeArguments)) return false;if (ownerType != null ? !ownerType.equals(that.ownerType) : that.ownerType != null) return false;return rawType != null ? rawType.equals(that.rawType) : that.rawType == null;}@Overridepublic int hashCode() {int result = actualTypeArguments != null ? Arrays.hashCode(actualTypeArguments) : 0;result = 31 * result + (ownerType != null ? ownerType.hashCode() : 0);result = 31 * result + (rawType != null ? rawType.hashCode() : 0);return result;}

使用TypeRefrence改写后的代码如下:

    private static void des3(String json) {Type type = new TypeReference<ResultDTO<OEVideoCreateResultDTO>>() {}.getType();CommonVO<SomeInfo> tmpResult =  JSON.parseObject(json, type);System.out.println(tmpResult);}

只要数据类型相同,那么key就是相同的,这也就保证了,只要数据类型相同,即使每次都是用新的 TypeRefrence 也不会产生上面的内存泄漏问题。

最后小编还尝试了GSON和 Jackson这两种工具,使用上面问题代码进行了反序列化压测,结果并没有出现内存泄露的问题,你知道为什么吗?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/152117.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Windows 下 Sublime Text 3.2.2 下载及配置

1 下载地址&#xff1a; https://www.sublimetext.com/3 Sublime Text 3.2.2 (此版本选择了 portable version)&#xff0c;直接解压就可以使用。 https://download.sublimetext.com/Sublime Text Build 3211.zip 2 相关配置 2.1 取消自动更新(修改完&#xff0c;需要注册码…

企业OA办公系统的设计与实现【附源码】

企业办公自动化系统设计与实现 毕业论文 摘 要 办公自动化&#xff08;Office Automation,简称OA&#xff09;是将现代化办公和计算机网络功能结合起来 的一种新型的办公方式。企业办公自动化系统在此基础上实现企业的快速运转和交流&#xff0c;进而 有效提高企业办公效率。 本…

Decoder-Only、Encoder-Only和Encoder-Decoder架构的模型区别、优缺点以及使用其架构的模型示例

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️ &#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

vue el-table字段点击出现el-input输入框,失焦保存

一、效果展示 当没有数据初始化展示如下&#xff1a; 有数据展示数据&#xff0c;点击出现输入框&#xff0c; 失焦保存修改 二、代码实现 <!-- cell-click"cellClick" 当前单击的单元格 --> <el-tableref"table"size"mini"height&qu…

笔记57:双向循环神经网络

本地笔记地址&#xff1a;D:\work_file\DeepLearning_Learning\03_个人笔记\3.循环神经网络\第9章&#xff1a;动手学深度学习~现代循环神经网络 a a a a a a a a a a a a

mysql面试题——存储引擎相关

一&#xff1a;MySQL 支持哪些存储引擎? MySQL支持多种存储引擎&#xff0c;比如InnoDB&#xff0c;MyISAM&#xff0c; MySQL大于等于5.5之后&#xff0c;默认存储引擎是InnoDB 二&#xff1a;InnoDB 和 MyISAM 有什么区别? InnoDB支持事务&#xff0c;MyISAM不支持InnoD…

html综合笔记:设计实验室主页

&#xff11; 主页来源及效果 Overview - Lab Website Template docs (gitbook.io) greenelab/lab-website-template: An easy-to-use, flexible website template for labs (github.com) 2 创建网页 3 主要的一些file 3.1 index.md 主页面 3.1.1 intro 3.1.2 highlight …

springBoot 配置druid多数据源 MySQL+SQLSERVER

1:pom 文件引入数据 <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.0</version> </dependency>…

Unity使用Visual Studio Code 调试

Unity 使用Visual Studio Code 调试C# PackageManager安装Visual Studio EditorVisual Studio Code安装Unity 插件修改Unity配置调试 PackageManager安装Visual Studio Editor 打开 Window->PackageManger卸载 Visual Studio Code Editor &#xff0c;这个已经被官方废弃安…

【C++ STL】string类-----迭代器(什么是迭代器?迭代器分哪几类?迭代器的接口如何使用?)

目录 一、前言 二、什么是迭代器 三、迭代器的分类与接口 &#x1f4a6;迭代器的分类 &#x1f4a6;迭代器的接口 &#x1f4a6;迭代器与接口之间的关联 四、string类中迭代器的应用 &#x1f4a6; 定义string类----迭代器 &#x1f4a6;string类中迭代器进行遍历 ✨be…

Python---数据序列类型之间的相互转换---list()方法:转化为列表。tuple() 方法转化为元组。set()方法:转换成集合。

list()方法&#xff1a;把某个序列类型的数据转化为列表 ------列表本身英文就是list 序列 &#xff1a;包括 字符串、列表、元组、集合以及字典。 # 1、定义 元组类型 的序列 tuple1 (10, 20, 30) print(list(tuple1))# 2、定义一个 集合类型的序列 set1 {a, b, c, d}…

NewStarCTF2023 Reverse Week3 EzDLL WP

分析 这里调用了z3h.dll中的encrypt函数。 用ida64载入z3h.dll 直接搜索encrypt 找到了一个XTEA加密。接着回去找key和密文。 发现key 这里用了个调试状态来判断是否正确&#xff0c;v71&#xff0c;要v7&#xff1d;1才会输出Right&#xff0c;即程序要处于飞调试状态。 可…

php-cli

//运行index.php ./php index.php//启动php内置服务器 ./php -S 0.0.0.0:8080//启动内置服务在后台运行&#xff0c;日志输出到本目录下的server.log nohup ./php -S 0.0.0.0:8080 -t . > server.log 2>&1 &# 查找 PHP 进程 ps aux | grep "php -S 0.0.0.0:…

趣学python编程 (五、常用IDE环境推荐)

Python环境指的是在计算机上安装Python解释器和相关的库&#xff0c;它是运行Python代码所必需的。那么开始Python编程前&#xff0c;准备安装好开发环境是前提。 默认的电脑上只是让人办公使用的&#xff0c;不带python编程开发环境。只有安装python环境&#xff0c;才可以编写…

记录下学的性能优化

一、性能优化的指标和工具 1.1 谷歌浏览器 拿淘宝网站为例,可以看到当前网页的加载信息 这个是瀑布图,瀑布图有横向和纵向 横向是具体的加载数据,悬浮看详情列表,可以看出下载时最后一个步骤,在这之前会先排队,浏览器会对优先级进行安排,它会对高优先级的请求优先请求.然后通…

Spring+Mybatis解析

源码执行流程 通过MapperScan导入MapperScannerRegistrar类MapperScannerRegistrar类实现了ImportBeanDefinitionRegistrar接口&#xff0c;Spring启动会调MapperScannerRegistrar类中的registerBeanDefinitions方法在registerBeanDefinitions方法中注册一个MapperScannerConf…

GFS分布式系统

GFS分布式文件系统 gfs glusterFS 开源的分布式的文件系统 存储服务器 客户端 以及网络&#xff08;NFS/samba&#xff09;网关 传统式&#xff08;老的&#xff09;分布式元服务系统&#xff0c;元服务器保存存储节点的目录树信息 一旦元服务器故障&#xff0c;所有的存储节点…

【C/PTA】函数专项练习(二)

本文结合PTA专项练习带领读者掌握函数&#xff0c;刷题为主注释为辅&#xff0c;在代码中理解思路&#xff0c;其它不做过多叙述。 目录 6-1 符号函数6-2 求排列数6-3 求一个大于10的n位整数w的后n-1位的数&#xff0c;并作为函数值返回。6-4 其右上三角&#xff08;含主对角线…

视频转码方法:多种格式视频批量转FLV视频的技巧

随着互联网的发展&#xff0c;视频已成为日常生活中不可或缺的一部分。然而&#xff0c;不同的视频格式可能适用于不同的设备和平台&#xff0c;因此需要进行转码。在转码之前&#xff0c;要了解各种视频格式的特点和适用场景。常见的视频格式包括MP4、AVI、MKV、FLV等。其中&a…

Java编程技巧:将图片导出成pdf文件

目录 一、pom依赖二、代码三、测试链接四、结果展示 一、pom依赖 <!-- pdf插件 start --> <dependency><groupId>com.itextpdf</groupId><artifactId>itextpdf</artifactId><version>5.5.3</version> </dependency> &l…