Jackson2.x中内存泄露的风险点—封装的intern逻辑

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Jackson2.x中内存泄露的风险点—封装的intern逻辑相关的知识,希望对你有一定的参考价值。

参考技术A

在 JAVA 语言中有8中基本类型和一种比较特殊的类型String。这些类型为了使他们在运行过程中速度更快,更节省内存,都提供了一种常量池的概念。常量池就类似一个JAVA系统级别提供的缓存。

8种基本类型的常量池都是系统协调的,String类型的常量池比较特殊。它的主要使用方法有两种:

直接使用双引号声明出来的String对象会直接存储在常量池中。
如果不是用双引号声明的String对象,可以使用String提供的intern方法。intern 方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中

正确使用intern方法,可以极大的减少内存使用空间,但是不当的使用intern方法,就会导致性能急剧下降。下面请看一个实际案例。

项目版本: Jackson:2.x、JDK1.8

问题原因: 业务侧在Jackson反序列化时,会调用String#intern方法,触发JDK的bug( https://bugs.openjdk.java.net/browse/JDK-8180048 )导致,这个bug会导致interned string得不到回收,从而导致内存泄露。

触发场景: 反序列化的对象为Map<Long,String>,但是key为userId,不收敛。这些userId的字符串都会进入常量池,由于G1的bug,GC时没有被回收,导致内存持续泄露。

解决方案1: Jaskson使用intern string的这个特效,可以通过配置进行关闭:

github上有关于Jackson.INTERN_FIELD_NAMES特效的讨论,见 https://github.com/FasterXML/jackson-core/issues/332 ,这个特性在2.x版本默认打开,在3.x版本默认关闭。
解决方案2: 升级JDK到192u

问题的根源就是反序列化时,key一般是固定的,若使用intern来处理,那么会大大节约反序列化时的空间,但是Map<Long,String>中的key因为是userId,所以会将大量的数据放入常量池中,从而导致内存泄露。

com.fasterxml.jackson.core.sym.CharsToNameCanonicalizer#_addSymbol

调用String#intern()方法有两个操作:

Jackson给出了一个缓存类,可以借鉴思索下:

深入解析String#intern

以上是关于Jackson2.x中内存泄露的风险点—封装的intern逻辑的主要内容,如果未能解决你的问题,请参考以下文章

java的内存泄露

(转)关于Jackson2.x中com.fasterxml.jackson包的用法

Android MVP-编程思想3(MVP-内存泄露问题处理,基类封装,有没有必要再使用软引用?)

OpenSSL之内存用法

内存泄露之常见问题解决--初级篇

一次查内存泄露