查看原文
其他

说了N次,请不要在JDK7及以上用Json-lib了...

作者:大魔王mAysWINd
来源:https://urlify.cn/NfmaYb

# Json-lib 介绍


Json-lib 是以前 Java 常用的一个 Json 库,最后的版本是 2.4,分别提供了 JDK 1.3 和 1.5 的支持,最后更新时间是 2010年12月14日。虽然已经很多年不维护了,但在搜索引擎上搜索 "Java Json" 等相关的关键词发现好像一直还有人在介绍和使用这个库。项目官网是 http://json-lib.sourceforge.net/。

# 一句话结论


Json-lib 在通过字符串解析每一个 Json 对象时,会对当前解析位置到字符串末尾进行 substring 操作,由于 JDK7 及以上的 substring 会完整拷贝截取后的内容,所以当遇到较大的 Json 数据并且含有较多对象时,会进行大量的字符数组复制操作,导致了大量的 CPU 和内存消耗,甚至严重的 Full GC 问题。

# 问题分析


某天发现线上生产服务器有不少 Full GC 问题,排查发现产生 Full GC 时某个老接口量会上涨,但这个接口除了解析 Json 外就是将解析后的数据存储到了缓存中,遂怀疑跟接口请求参数大小有关,打日志发现确实有比一般请求大得多的 Json 数据,但也只有 1MB 左右。为了简化这个问题,编写如下的性能测试代码。
package net.mayswind;
import net.sf.json.JSONObject;import org.apache.commons.io.FileUtils;
import java.io.File;

public class JsonLibBenchmark {public static void main(String[] args) throws Exception { String data = FileUtils.readFileToString(new File("Z:\\data.json")); benchmark(data, 5); }
private static void benchmark(String data, int count) {long startTime = System.currentTimeMillis();
for (int i = 0; i < count; i++) { JSONObject root = JSONObject.fromObject(data); }
long elapsedTime = System.currentTimeMillis() - startTime; System.out.println(String.format("count=%d, elapsed time=%d ms, avg cost=%f ms", count, elapsedTime, (double) elapsedTime / count)); }}
上述代码执行后平均每次解析需要 7秒左右才能完成,如下图所示。
测试用的 Json 文件,“...” 处省略了 34,018 个相同内容,整个 Json 数据中包含了 3万多个 Json 对象,实际测试的数据如下图所示。
{"data": [ {"foo": 0123456789,"bar": 1234567890 }, {"foo": 0123456789,"bar": 1234567890 }, ... ]}
使用 Java Mission Control 记录执行的情况,如下图所示,可以看到分配了大量 char[] 数组。
翻看相关源码,其中 JSONObject._fromJSONTokener 方法主要内容如下所示。可以看到其在代码一开始就匹配是否为 "null" 开头。
private static JSONObject _fromJSONTokener(JSONTokener tokener, JsonConfig jsonConfig) {try {if (tokener.matches("null.*")) { fireObjectStartEvent(jsonConfig); fireObjectEndEvent(jsonConfig);return new JSONObject(true); } else if (tokener.nextClean() != '{') {throw tokener.syntaxError("A JSONObject text must begin with '{'"); } else { fireObjectStartEvent(jsonConfig); Collection exclusions = jsonConfig.getMergedExcludes(); PropertyFilter jsonPropertyFilter = jsonConfig.getJsonPropertyFilter(); JSONObject jsonObject = new JSONObject();...
而 matches 方法更是直接用 substring 截取当前位置到末尾的字符串,然后进行正则匹配。
public boolean matches(String pattern) {String str = this.mySource.substring(this.myIndex);return RegexpUtils.getMatcher(pattern).matches(str);}
字符串 substring 会传入字符数组、起始位置和截取长度创建一个新的 String 对象。
public String substring(int beginIndex) {if (beginIndex < 0) {throw new StringIndexOutOfBoundsException(beginIndex); }int subLen = value.length - beginIndex;if (subLen < 0) {throw new StringIndexOutOfBoundsException(subLen); }return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);}
在 JDK7 及以上,调用该构造方法时在最后一行会复制一遍截取后的数据,这也是导致整个问题的关键所在了。
public String(char value[], int offset, int count) {if (offset < 0) {throw new StringIndexOutOfBoundsException(offset); }if (count <= 0) {if (count < 0) {throw new StringIndexOutOfBoundsException(count); }if (offset <= value.length) {this.value = "".value;return; } }// Note: offset or count might be near -1>>>1.if (offset > value.length - count) {throw new StringIndexOutOfBoundsException(offset + count); }this.value = Arrays.copyOfRange(value, offset, offset+count);}
你用过Json-lib吗?留言说一下。

热文推荐


震惊,Java中一个存在十几年的bug...

全球最大的色情网站,保留着西方媒体最后的良心

返工潮之下,数据告诉你,哪些城市面临最大的疫情防扩压力?



点我,查看更多精彩文章。

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存