专栏首页JAVA葵花宝典请不要在 JDK 7+ 中使用这个 JSON 包了

请不要在 JDK 7+ 中使用这个 JSON 包了

来源:大魔王mAysWINd

cnblogs.com/mayswind/p/9222245.html

  • Json-lib 介绍
  • 一句话结论
  • 问题分析

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);
}

本文分享自微信公众号 - JAVA葵花宝典(Javakhbd)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-03-02

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Hutool中那些常用的工具类和方法

    获取classPath下的文件,在Tomcat等容器下,classPath一般是WEB-INF/classes。

    JAVA葵花宝典
  • SpringBoot 应用整合JWT详解

    Spring Boot、OAuth 2.0、JWT、Spring Security、SSO、UAA

    JAVA葵花宝典
  • SpringBoot优雅地发送邮件

    消息通知的形式也有很多,比如:短信、邮件、app推送等,本文主要给大家描述一下邮件通知的形式,因为邮件相比较其他通知渠道更方便实用(免费),除了简单文本邮件(已...

    JAVA葵花宝典
  • 请不要在 JDK 7+ 中使用这个 JSON 包了!

    Json-lib 是以前 Java 常用的一个 Json 库,最后的版本是 2.4,分别提供了 JDK 1.3 和 1.5 的支持,最后更新时间是 2010年1...

    搜云库技术团队
  • 极光推送_总结_01_Java实现极光推送

    shirayner
  • 系统学习javaweb-05-网络编程

    csxiaoyao
  • 重要开源!CNN-RNN-CTC 实现手写汉字识别

    手写汉字的一些特点: ①基本笔画变化。印刷体汉字的笔画基本上是横平竖直,折笔(乛、乙、く)的拐角大都是尖锐的钝角、锐角或直角,因而折笔基本上可以看做是由折线段...

    机器学习AI算法工程
  • 通常Java开发人员如何进行数据排序?

    在实际工作中和平时学习中,以及分析开源Java项目的大量源代码后,我发现Java开发人员通常使用两种方法。一是使用Collections或 Arrays的 so...

    用户1289394
  • WMS仓库管理系统简介

    WMS软件和进销存管理软件的最大区别在于:进销存软件的目标是针对于特定对象(如仓库)的商品、单据流动,是对于仓库作业结果的记录、核对和管理--报警、报表、结果分...

    物流IT圈
  • 教程 | 预测电影偏好?如何利用自编码器实现协同过滤方法

    机器之心

扫码关注云+社区

领取腾讯云代金券