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

点击上方“终端研发部”,选择星标

回复“资源”,领取全网最火的Java核心知识总结~

作者:大魔王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吗?留言说一下。

阅读更多

微信支付的跨平台架构详解

Flutter  + MVP +Kotlin 实战!

Tomcat 竟然有 bug,这我能信?

互联网的圈子,游戏行业的现状是如何?

动画:一招学会TCP的三次握手和四次挥手

干掉PostMan!IDEA这款插件太实用了…

美团面试题:Java-线程池 ThreadPool 专题详解

在这里获得的不仅仅是技术!

喜欢就给个“在看

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值