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…

为什么阿里推荐 LongAdder ,不推荐 AtomicLong ??

1.什么是LongAdder LongAdder是JDK1.8由Doug Lea大神新增的原子操作类&#xff0c;位于java.util.concurrent.atomic包下&#xff0c;LongAdder在高并发的场景下会比AtomicLong 具有更好的性能&#xff0c;代价是消耗更多的内存空间。

python+Django 使用apscheduler实现定时任务 管理调度

apscheduler实现定时任务 管理调度 在Django 项目中经常会用到定时任务去处理一些业务处理 使用 APScheduler 可以轻松地实现定时任务的管理和调度。你可以通过以下步骤来创建、启动、停止和删除定时任务&#xff1a; 1.创建调度器对象&#xff1a; from apscheduler.schedu…

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

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

sql server外键设置

SQL Server外键设置 简介 在关系型数据库中&#xff0c;外键是一种约束&#xff0c;用于确保数据的完整性和一致性。外键约束定义了一个表中的列与另一个表中的列之间的关系&#xff0c;它可以用来保证数据的一致性、防止数据的破坏和数据冗余。在SQL Server中&#xff0c;我们…

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

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

Bcrypt 加密算法

Bcrypt 加密算法研究与对比 Bcrypt解密工具下载&#xff08;暴力破解&#xff0c;需基于密码本&#xff09;

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…

【课程文章】微信小程序学习指南

本文章是配合【微信小程序】视频教程的指南目录文章 准备软件&#xff1a;这些软件可以在&#xff08;点击这里&#xff09;【微课百家】下载。 1.MySQL数据&#xff1b; 2.Navicat工具&#xff1b; 3.TodoDemo或者TeachDemo&#xff0c;配合小程序使用的后端对接平台源代码…

数据结构【DS】数组

在应用题中&#xff0c;“数组”常结合“矩阵压缩存储”考察&#xff0c;此类题目需要注意以下条件&#xff1a; ✧ 行优先存储 or 列优先存储&#xff1f; ✧ 矩阵下标从 1 or 0 开始&#xff1f;——若题目未特别说明&#xff0c;矩阵下标默认从1开始 ✧ 数组下标从 0 or 1 开…

【Spring Boot】如何集成Redis

在pom.xml文件中导入spring data redis的maven坐标。 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency> 在application.yml文件中加入redis相关配置。 spr…

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;即程序要处于飞调试状态。 可…

如何建设一个高效的中英文外贸网站?

随着全球化的趋势&#xff0c;越来越多的企业需要搭建中英文网站来拓展国际业务。搭建中英文网站不仅可以提高企业在国际市场的知名度和影响力&#xff0c;还可以增加与海外客户的交流和合作机会。企业不仅需要有一个出色的英文网站来吸引国际客户&#xff0c;还需要有一个精准…