FastJson不成想还有个版本2啊:序列化大字符串报错
背景
发现陷入了一个怪圈,写文章的话,感觉只有大bug或比较值得写的内容才会写,每次一写就是几千字,争取写得透彻一些,但这样,我也挺费时间,读者也未必有这么多时间看。
我想着,日常遇到的小bug、平时工作中的一些小的心得体会,都还是可以写写,这样也才是最贴近咱们作为一线开发生活的,也不必非得是个完整且深入的主题,因此,准备搞一个专门的标签:点滴记录Coding之路来记录这些。
ok,咱们开始,最近,手下开发小哥去帮忙做一个其他组的项目,但遇到一些解决不了的问题就会找我帮忙看。最近来问我了一个问题,说是他有个接口,调用会报内存溢出,在本机就能复现,不知道咋回事。
上下文
接口代码如下:
在一个for循环里面,会去执行sql,查询数据库记录,存到dataList这个列表中,然后序列化为json,这里呢,他们使用的是fastjson。
他调用接口给我演示了下,上面代码不是个循环嘛,跑着跑着就报错了,报错的栈大概如下(这个栈来自网上,问题类似):
Exception in thread "pool-4-thread-1" java.lang.OutOfMemoryError
at com.alibaba.fastjson2.JSONWriterUTF16.writeNameRaw(JSONWriterUTF16.java:561)
at com.alibaba.fastjson2.writer.FieldWriterImpl.writeFieldName(FieldWriterImpl.java:143)
at com.alibaba.fastjson2.writer.ObjectWriter_3.write(Unknown Source)
at com.alibaba.fastjson2.writer.ObjectWriterImplList.write(ObjectWriterImplList.java:278)
at com.alibaba.fastjson2.JSON.toJSONString(JSON.java:1757)
.....
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
排查
刚看到这个,也没啥思路,一开始还以为是内存、gc之类的问题,看了会后,决定在报错的地方打断点看下,到底为啥报这个。
我这边debug了两圈后,发现都是走到如下位置的时候报错:
这个函数,大概就是,在初步序列化对象为字符串后,要计算字符串的长度,然后看看这个长度能不能写入到底层JsonWriter的字符数组中(会比较字符串的长度和JsonWriter中数组的长度),如果JsonWriter中数组长度过小,这里就要触发扩容。
而扩容前,如果发现要扩容的大小大于maxArraySize(一个配置项),就会抛这个内存溢出的溢出,并不是真的发生了内存溢出。
当时debug的时候,看到maxArraySize大概是60w多,大概就是60多m大小。当时就很纳闷,是不是查出来的数据太大了,不然即使扩容啥的,也不可能大于60M,后面果然看到数据竟然达到了几十M大小,由于这个系统我也没参与,这块业务合不合理就不管了,解决问题就行。
然后我就看了下,maxArraySize赋值的地方,看看这个能不能改大点,改大了就没事了。
protected final int maxArraySize;
protected JSONWriter(Context context, Charset charset) {
this.context = context;
this.charset = charset;
this.utf8 = charset == StandardCharsets.UTF_8;
this.utf16 = charset == StandardCharsets.UTF_16;
quote = (context.features & Feature.UseSingleQuotes.mask) == 0 ? '"' : '\'';
// 64M or 1G
maxArraySize = (context.features & LargeObject.mask) != 0 ? 1073741824 : 67108864;
}
这边果然看到,有个注释,64M OR 1G,果然,是个配置项,看起来,这个配置项是受LargeObject这个控制的。
一开始,我以为这个是com.alibaba.fastjson.serializer.SerializerFeature
里的枚举项,结果并不是,没发现是JsonWriter的配置项:
com.alibaba.fastjson2.JSONWriter.Feature
知道是配置项了,问题是怎么配置呢?仔细看了各个方法,都不能传这种JsonWriter的枚举啊
后边,看了半天,发现这个方法可以传JsonWriter的feature:
问题是,这个defaultFeatures是int,32位整数,每个bit代表一个特性,也就是说,我得自己计算将LargeObject这个bit置为1后,整个int的值。
大家看这个feature的值:
// 十进制为:8589934592, 二进制为:001000000000000000000000000000000000
LargeObject(1L << 33),
我就根据这个,自己把这个bit设为1,然后算了个值出来,结果,跟我说,超过了int的范围,导致我没法传参进去。
解决
我都服了,然后开始在网上看看有没有类似的问题,结果只找到了一篇文章。
https://blog.csdn.net/m0_68736501/article/details/132078314
解决办法是说,升级jar包版本到2.0.16,里面有个方法,可以传JsonWriter的Feature枚举值进去:
JSON.toJSONString(t, JSONWriter.Feature.WriteClassName, JSONWriter.Feature.LargeObject).getBytes(DEFAULT_CHARSET);
结果我看了我们版本,都2.0.19了,版本比他还高,结果没看到这个方法。服了,难道高版本还把这个方法删了?
然后小伙子看我忙,就说他回去再研究研究,我说行,我也网上查下。
后边也找到篇文章,让他试试:https://www.exyb.cn/news/show-5352725.html,他没说有没有效果,但是过了一阵,他跟我说,知道问题了。
行吧,我给大家梳理下结论,我们的pom引入的依赖是:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.19</version>
</dependency>
这个内部其实还依赖了另外的jar:
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2-extension</artifactId>
</dependency>
而上面的这个,又依赖了:
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
</dependency>
差不多,就是下图这样的关系:
然后,导致我们项目中,其实有两个JSON类:
com.alibaba.fastjson2.JSON; 位于fastjson2-2.0.19.jar
com.alibaba.fastjson.JSON; 位于fastjson-2.0.19.jar
而之前我们导入的是下面那个,也就是传统的com.alibaba.fastjson.JSON,里面就是没法传JsonWriter的Feature枚举的,只有上面那个才有:
com.alibaba.fastjson2.JSON#toJSONString(java.lang.Object, com.alibaba.fastjson2.JSONWriter.Feature...)
/**
* Serialize Java Object to JSON {@link String} with specified {@link JSONReader.Feature}s enabled
*
* @param object Java Object to be serialized into JSON {@link String}
* @param features features to be enabled in serialization
*/
static String toJSONString(Object object, JSONWriter.Feature... features) {
所以,剩下的事情,简单了,修改import的类为com.alibaba.fastjson2.JSON即可,然后序列化时传入feature:
String previewDataJson = JSON.toJSONString(dataList,LargeObject);
问题解决。
结论
新项目建议还是用jackson算了,当然了,这个项目也不是我主导,而且都开发快完成了,就这样吧,一般大问题也没有,有就再改吧。