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方法进行进行了重写:

@Override
    public 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.equals
        if (!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;

    }

    @Override
    public 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.mfbz.cn/a/165061.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

文件重命名最佳实践:如何确保文件名的准确性和一致性

在日常生活和工作中&#xff0c;经常需要处理大量的文件&#xff0c;包括电子文件和纸质文件。文件重命名是为了更好地组织和管理这些文件&#xff0c;以方便查找和使用。然而&#xff0c;重命名文件并不是一件简单的事情&#xff0c;它需要遵循一定的最佳实践以确保文件名的准…

视频封面:从视频中提取封面,轻松制作吸引人的视频

在当今的数字时代&#xff0c;视频已成为人们获取信息、娱乐和交流的重要方式。一个吸引人的视频封面往往能抓住眼球&#xff0c;提高点击率和观看率。今天将介绍如何从视频中提取封面&#xff0c;轻松制作吸引人的视频封面。 一、准备素材选择合适的视频片段 首先&#xff0…

母婴服务预约小程序的效果如何

二胎家庭增速明显&#xff0c;占比较大&#xff0c;成为市场各母婴品牌的目标&#xff0c;而随着行业发展及市场变化&#xff0c;线上互联网深入人们生活&#xff0c;各家母婴品牌开始向“数字化”靠拢。 目前母婴门店商家主要面临服务/产品线上曝光不足、宣传度不够或扩圈无门…

JAVA小游戏 “拼图”

第一步是创建项目 项目名自拟 第二部创建个包名 来规范class 然后是创建类 创建一个代码类 和一个运行类 代码如下&#xff1a; package heima;import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import jav…

EDA实验-----4*4矩阵键盘与数码管显示测试

目录 一、实验目的 二、实验仪器设备 三、实验原理 四、实验要求 五、实验步骤 六、实验报告 七、实验过程 1.矩阵键盘按键原理 2.数码管原理 3.分频器代码 4.电路图连接 5.文件烧录 一、实验目的 了解数码管的工作原理&#xff1b;掌握4*4矩阵键盘和数码管显示的编…

UnitTest框架

目标&#xff1a; 1.掌握UnitTest框架的基本使用方法 2.掌握断言的使用方法 3.掌握如何实现参数化 4.掌握测试报告的生成 1.定义 &#xff08;1&#xff09;框架(framework)&#xff1a;为解决一类事情的功能集合。&#xff08;需要按照框架的规定(套路) 去书写代码&…

Virtual安装centos后,xshell连接centos

1. 网络使用Host-Only模式动态分配IP&#xff0c;运行 system restart network 后&#xff0c;使用ifconfig查看新的ip&#xff0c;XShell可以直接连上centos&#xff0c; 但是由于使用的是Host-Only模式&#xff0c;centos不能访问网络&#xff0c;只能与宿主机相互通信 2. 网…

快速支持客户知识库的核心优势是什么?

快速支持客户知识库是一个集中存储和组织企业知识的平台&#xff0c;包含了丰富的信息和解决方案&#xff0c;以帮助客户快速解决问题&#xff0c;帮助企业提高客户支持效率和满意度。那么&#xff0c;快速支持客户知识库的核心优势是什么呢&#xff1f; | 1、提高客户自助支持…

VBA之Word应用:文档(Document)的书签

《VBA之Word应用》&#xff08;版权10178982&#xff09;&#xff0c;是我推出第八套教程&#xff0c;教程是专门讲解VBA在Word中的应用&#xff0c;围绕“面向对象编程”讲解&#xff0c;首先让大家认识Word中VBA的对象&#xff0c;以及对象的属性、方法&#xff0c;然后通过实…

win10无损升级到win11

1&#xff0c;下载win11升级助手 https://download.microsoft.com/download/5/4/c/54c22b82-d0cd-4e34-9a06-b75823a8aede/Windows11InstallationAssistant.exe 2&#xff0c;启动助手开始安装 安装时需要重启数次 3&#xff0c;安装后界面 4&#xff0c;安装后&#xff0c…

ACWSpring1.3

首先,前端写ajax写上我们的访问路径(就在我们前端的源代码里面),我们建了两个包pkController用于前端页面url映射过来一层一层找到我们的RestController返回bot1里面有键值,返回的这就是一个session对象bot1这个map.前端拿到我们bot1里的两个值给到我们前端显示出来 1准备页面:…

苹果签名应用掉签频繁原因排查,以及如何避免

作为一个对iOS生态有着深厚理解的实用技术博主&#xff0c;我明白苹果签名应用掉签对我们的开发和使用带来的困扰。签名在苹果设备中扮演着至关重要的角色&#xff0c;它不仅确保了应用来源的合法性&#xff0c;也影响着应用的顺畅运行。 今天&#xff0c;我将和您一同探讨苹果…

云存储与物理存储:优缺点对比分析

当您需要存储数字文件时&#xff0c;您有两个基本选择&#xff1a;云存储和物理存储。 云存储允许您通过互联网将文件保存在云存储提供商运营的服务器上。这些公司通常在多个数据中心制作文件的备份副本&#xff0c;并使用复杂的加密来保护它们。您可以从任何连接互联网的设备访…

ZC序列理论学习及仿真

文章目录 前言一、ZC 序列理论1、基本概念2、表达式3、ZC 序列一些定义①、自相关②、循环移位③、循环自相关④、循环互相关二、ZC 序列性质1、性质 1:恒包络,即等模2、性质 2:零循环自相关3、性质 3:固定循环互相关4、其他性质①、傅里叶变换后仍是 ZC 序列②、低峰均比③…

docker的基本使用以及使用Docker 运行D435i

1.一些基本的指令 1.1 容器 要查看正在运行的容器&#xff1a; sudo docker ps 查看所有的容器&#xff08;包括停止状态的容器&#xff09; sudo docker ps -a 重新命名容器 sudo docker rename <old_name> <new_name> <old_name> 替换为你的容器名称…

基环树(pseudotree)入门

目录 无向基环树找环&#xff0c;[题目](https://www.luogu.com.cn/problem/P8655)拓扑排序找环并查集找环dfs找环 内向基环树[2876. 有向图访问计数](https://leetcode.cn/problems/count-visited-nodes-in-a-directed-graph/description/)[2127. 参加会议的最多员工数](https…

leetcode34.排序数组中查找元素第一个和最后一个位置两种解题方法(超详细)

34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/description/?envTypelist&envIdZCa7r67M这道题&#xff0c;读者可能会说这道题有什么好…

Flutter笔记:拖拽手势

Flutter笔记 拖拽手势 作者&#xff1a;李俊才 &#xff08;jcLee95&#xff09;&#xff1a;https://blog.csdn.net/qq_28550263 邮箱 &#xff1a;291148484163.com 本文地址&#xff1a;https://blog.csdn.net/qq_28550263/article/details/134485123 目 录 1. 概述2. 垂直拖…

argocd

部署argocd https://github.com/argoproj/argo-cd/releases kubectl create namespace argocd kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/v2.9.1/manifests/install.yaml官网 https://argo-cd.readthedocs.io/en/stable/ kubectl crea…

从 0 开始手写一个 Mybatis 框架,三步搞定!

MyBatis框架的核心功能其实不难&#xff0c;无非就是动态代理和jdbc的操作&#xff0c;难的是写出来可扩展&#xff0c;高内聚&#xff0c;低耦合的规范的代码。本文完成的Mybatis功能比较简单&#xff0c;代码还有许多需要改进的地方&#xff0c;大家可以结合Mybatis源码去动手…
最新文章