來源:大魔王mAysWINd
www.cnblogs.com/mayswind/p/9222245.html
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 = https://www.cnblogs.com/javastack/p/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);
}
推薦去我的博客閱讀更多:
1.Java JVM、集合、多執行緒、新特性系列教程
2.Spring MVC、Spring Boot、Spring Cloud 系列教程
3.Maven、Git、Eclipse、Intellij IDEA 系列工具教程
4.Java、后端、架構、阿里巴巴等大廠最新面試題
覺得不錯,別忘了點贊+轉發哦!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/162684.html
標籤:Java
下一篇:Java_圖片轉字符
