Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >请不要在 JDK 7+ 中使用这个 JSON 包了

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

作者头像
JAVA葵花宝典
发布于 2020-03-04 08:25:36
发布于 2020-03-04 08:25:36
73700
代码可运行
举报
文章被收录于专栏:JAVA葵花宝典JAVA葵花宝典
运行总次数:0
代码可运行

来源:大魔王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 左右。为了简化这个问题,编写如下的性能测试代码。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 对象,实际测试的数据如下图所示。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
{
    "data":
    [
        {
            "foo": 0123456789,
            "bar": 1234567890
        },
        {
            "foo": 0123456789,
            "bar": 1234567890
        },
        ...
    ]
}

使用 Java Mission Control 记录执行的情况,如下图所示,可以看到分配了大量 char[] 数组。

翻看相关源码,其中 JSONObject._fromJSONTokener 方法主要内容如下所示。可以看到其在代码一开始就匹配是否为 "null" 开头。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 截取当前位置到末尾的字符串,然后进行正则匹配。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public boolean matches(String pattern) {
    String str = this.mySource.substring(this.myIndex);
    return RegexpUtils.getMatcher(pattern).matches(str);
}

字符串 substring 会传入字符数组、起始位置和截取长度创建一个新的 String 对象。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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 及以上,调用该构造方法时在最后一行会复制一遍截取后的数据,这也是导致整个问题的关键所在了。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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);
}

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-03-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 JAVA葵花宝典 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
Java中的substring真的会引起内存泄露么?
在Java中开发,String是我们开发程序可以说必须要使用的类型,String有一个substring方法用来截取字符串,我们想必也常常使用。但是你知道么,关于Java 6中的substring是否会引起内存泄露,在国外的论坛和社区有着一些讨论,以至于Java官方已经将其标记成bug,并且为此Java 7 还重新进行了实现。读到这里可能你的问题就来了,substring怎么会引起内存泄露呢?那么我们就带着问题,走进小黑屋,看看substring有没有内存泄露,又是怎么导致所谓的内存泄露。
技术小黑屋
2018/09/05
8850
Java API:String class 原
上面由API提供的描述,可以看出,String是一个最终类,继承了Object类,实现了序列化接口和排序接口以及char可读序列接口。可以得出以下几个特点。
云飞扬
2019/03/12
1.2K0
你敢信?String类竟然是导致生产环境频繁内存溢出的罪魁祸首!!
排查问题的整个过程相当耗时,这里,我就直接说定位到的问题吧。后面,我会单独写一篇详细的排查问题过程的文章!
冰河
2020/10/29
7300
你敢信?String类竟然是导致生产环境频繁内存溢出的罪魁祸首!!
走进 JDK 之 String
贯穿全文,你需要始终记住这句话,String 是不可变类 。其实前面说过的所有基本数据类型包装类都是不可变类,但是在 String 的源码中,不可变类 的概念体现的更加淋漓尽致。所以,在阅读 String 源码的同时,抽丝剥茧,你会对不可变类有更深的理解。
路遥TM
2021/08/31
3150
Java String 源码分析
String 是final 类型不能被继承,同时实现了 java.io.serializable Comparable charSequence 三个接口。
王小明_HIT
2020/10/23
3880
String-源码阅读
上文:jdk-8大基础类型源码阅读(byte、short、int、long、float、double、boolean、char)
逍遥壮士
2022/12/01
2880
Java String类源码阅读笔记
本文基于jdk1.8 String类可谓是我们开发中使用最多的一个类了。对于它的了解,仅仅限于API的了解是不够的,必须对它的源码进行一定的学习。
三分恶
2020/09/22
4950
Java String类源码阅读笔记
JDK源码解析之 Java.lang.String
String类是一个被final修饰的常量类,常量类的特性为不可被任何类所继承,一旦String对象被创建,该对象是无法被改变的,直至该对象被销毁(特殊情况除外:如暴力反射)。
栗筝i
2022/12/01
3240
Java基础-字符串
String类的substring方法可以从一个较大的字符串中提取出一个子串,例如:
用户5252199
2022/04/18
3050
深入理解Java常用类----String(二)
根据文章内容总结,该文主要介绍了Java中的String类的源码,包括字符串的创建、拼接、转化、比较、连接、空指针、缓存、正则表达式、字符串拼接、分隔符、替换、大小写、比较、数组、StringBuilder、多行字符串、正则表达式、字符串缓存、字符串拼接、以及自定义字符串等。此外,还介绍了String类在Java中的使用方式,包括字符串的创建、拼接、转化、比较、连接、空指针、缓存、正则表达式、字符串拼接、分隔符、替换、大小写、比较、数组、StringBuilder、多行字符串、正则表达式、字符串缓存、字符串拼接、以及自定义字符串等。
Single
2018/01/04
8180
深入理解Java常用类----String(二)
JAVA类String
今天要讲的是JDK中的String类了,相信大家对这个类特别的熟悉,那今天话不多说,直接讲一些常用的方法。
用户6055494
2019/12/15
5850
一文看完String的前世今生,内容有点多,请耐心看完!
String字符串作为一种引用类型,在Java中的地位举足轻重,也是代码中出现频率最高的一种数据结构,因此,我们需要像分析Object一样,将String作为一个topic,单独拿出来总结,这里面涉及到字符串的不可变性,字符串拼接、存储、比较、截取以及StringBuffer,StringBuilder区别等。
JavaBuild
2024/05/27
1290
一文看完String的前世今生,内容有点多,请耐心看完!
JUC学习之不可变
有很大几率出现 java.lang.NumberFormatException 或者出现不正确的日期解析结果,例如:
大忽悠爱学习
2022/01/10
2620
JUC学习之不可变
零基础学Java(4)字符串
从概念上讲,Java字符串就是Unicode字符序列。例如,字符串"Java\u2122"由5个Unicode字符J、a、v、a和™组成。Java没有内置的字符串类型,而是在标准Java类库中提供了一个预定义类,很自然地叫做String。每个双引号括起来的字符串都是String类中的一个实例
全栈程序员站长
2022/09/19
3910
String substring的内存泄漏分析
从上述的源代码可以看出,使用substring获取子字符串方法中,原有字符串的内容value(char[])将继续重用。
孟君
2023/02/23
4010
String substring的内存泄漏分析
String、StringBuilder、StringBuffer 用法比较
String、StringBuilder、StringBuffer 三个类源自JDK的 java/lang/ 目录下:
阳光岛主
2019/02/19
6320
JDK1.8源码(三)——java.lang.String 类
  String 类也是java.lang 包下的一个类,算是日常编码中最常用的一个类了,那么本篇博客就来详细的介绍 String 类。 1、String 类的定义 public final class String implements java.io.Serializable, Comparable<String>, CharSequence {}   和上一篇博客所讲的 Integer 类一样,这也是一个用 final 声明的常量类,不能被任何类所继承,而且一旦一个String对象被创建, 包
IT可乐
2018/03/30
9610
JDK1.8源码(三)——java.lang.String 类
美团面试题:String s = new String("111") 会创建几个对象?
> 公众号:[Java小咖秀](https://t.1yb.co/jwkk),网站:[javaxks.com](https://www.javaxks.com)
Java小咖秀
2021/04/11
6020
美团面试题:String s = new String("111") 会创建几个对象?
JUC学习笔记——共享模型之不可变
但是我们可以选择更换一种日期类型,我们选择不可改变的日期类就可以完成并发下的数据修改问题:
秋落雨微凉
2022/11/21
3260
JDK1.8源码(三)——java.lang.String 类
  String 类也是java.lang 包下的一个类,算是日常编码中最常用的一个类了,那么本篇博客就来详细的介绍 String 类。
全栈程序员站长
2022/09/07
3620
JDK1.8源码(三)——java.lang.String 类
相关推荐
Java中的substring真的会引起内存泄露么?
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验