FastJson不成想還有個版本2啊:序列化大字符串報錯

背景

發現陷入了一個怪圈,寫文章的話,感覺只有大bug或比較值得寫的內容纔會寫,每次一寫就是幾千字,爭取寫得透徹一些,但這樣,我也挺費時間,讀者也未必有這麼多時間看。

我想着,日常遇到的小bug、平時工作中的一些小的心得體會,都還是可以寫寫,這樣也纔是最貼近咱們作爲一線開發生活的,也不必非得是個完整且深入的主題,因此,準備搞一個專門的標籤:點滴記錄Coding之路來記錄這些。

ok,咱們開始,最近,手下開發小哥去幫忙做一個其他組的項目,但遇到一些解決不了的問題就會找我幫忙看。最近來問我了一個問題,說是他有個接口,調用會報內存溢出,在本機就能復現,不知道咋回事。

上下文

接口代碼如下:

image-20230823204401353

在一個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了兩圈後,發現都是走到如下位置的時候報錯:

image-20230823205859412

這個函數,大概就是,在初步序列化對象爲字符串後,要計算字符串的長度,然後看看這個長度能不能寫入到底層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

image-20230823211425683

知道是配置項了,問題是怎麼配置呢?仔細看了各個方法,都不能傳這種JsonWriter的枚舉啊

image-20230823211542689

後邊,看了半天,發現這個方法可以傳JsonWriter的feature:

image-20230823211711781

問題是,這個defaultFeatures是int,32位整數,每個bit代表一個特性,也就是說,我得自己計算將LargeObject這個bit置爲1後,整個int的值。

大家看這個feature的值:

// 十進制爲:8589934592, 二進制爲:001000000000000000000000000000000000
LargeObject(1L << 33),

image-20230823212200413

我就根據這個,自己把這個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,他沒說有沒有效果,但是過了一陣,他跟我說,知道問題了。

1692797247178

行吧,我給大家梳理下結論,我們的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>

差不多,就是下圖這樣的關係:

image-20230823213233794

image-20230823213345555

然後,導致我們項目中,其實有兩個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算了,當然了,這個項目也不是我主導,而且都開發快完成了,就這樣吧,一般大問題也沒有,有就再改吧。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章