號(hào)稱引領(lǐng)下一個(gè)十年的高性能JSON庫fastjson2登場了
往期熱門文章:
1、Lombok同時(shí)使?@Data和@Builder 的坑 2、IntelliJ IDEA快捷鍵大全 + 動(dòng)圖演示,建議收藏! 3、如何防止你的 jar 被反編譯? 4、大公司為什么禁止SpringBoot項(xiàng)目使用Tomcat? 5、Java 8 的 Stream 不好調(diào)試?別逗了!IDEA 調(diào)試就能輕松搞定?。?/a>
概述
FastJson2是FastJson項(xiàng)目的重要升級(jí),目標(biāo)是為下一個(gè)十年提供一個(gè)高性能的JSON庫。根據(jù)官方給出的性能來看,相比v1版本,確實(shí)有了很大的提升,本篇文章我們來看下究竟做了哪些事情,使得性能有了大幅度的提升。代碼測試 + 源碼閱讀的方式對(duì)FastJson2的性能提升做一個(gè)較為全面的探索。一、環(huán)境準(zhǔn)備
fastjson和fastjson2。使用兩個(gè)版本進(jìn)行對(duì)比試驗(yàn)。
1.1 引入對(duì)應(yīng)依賴
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.79</version>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.8</version>
</dependency>
1.2 創(chuàng)建測試類
創(chuàng)建類:Student.java
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class Student {
private String name;
private Integer age;
private String address;
public Student(String name, Integer age, String address) {
this.name = name;
this.age = age;
this.address = address;
}
}
創(chuàng)建測試main方法:
/**
* 定義循環(huán)次數(shù)
*/
private final static Integer NUM = 100;
public static void main(String[] args) {
// 總時(shí)間
long totalTime = 0L;
//初始化學(xué)生數(shù)據(jù)
List<Student> studentList = new ArrayList<>();
// 10w學(xué)生
for (int i = 0; i < 100000; i++) {
studentList.add(Student.builder().name("我犟不過你").age(10).address("黑龍江省哈爾濱市南方區(qū)哈爾濱大街267號(hào)").build());
}
// 按指定次數(shù)循環(huán)
for (int i = 0; i < NUM; i++) {
// 單次循環(huán)開始時(shí)間
long startTime = System.currentTimeMillis();
// 遍歷學(xué)生數(shù)據(jù)
studentList.forEach(student -> {
// 序列化
String s = JSONObject.toJSONString(student);
//字符串轉(zhuǎn)回java對(duì)象
JSONObject.parseObject(s, Student.class);
});
// 將學(xué)生list序列化,之后轉(zhuǎn)為jsonArray
JSONArray jsonArray = JSONArray.parseArray(JSONObject.toJSONString(studentList));
// 將jsonArray轉(zhuǎn)java對(duì)象list
jsonArray.toJavaList(Student.class);
//單次處理時(shí)間
long endTime = System.currentTimeMillis();
// 單次耗時(shí)
totalTime += (endTime - startTime);
System.out.println("單次耗費(fèi)時(shí)間:" + (endTime - startTime) + "ms");
}
System.out.println("平均耗費(fèi)時(shí)間:" + totalTime / NUM + "ms");
}
jsonArray.toJavaList方法轉(zhuǎn)變成了jsonArray.toList。二、性能測試
128M,以免造成偏差:
2.1 第一次測試
fastjson結(jié)果
單次耗費(fèi)時(shí)間:863ms
單次耗費(fèi)時(shí)間:444ms
單次耗費(fèi)時(shí)間:424ms
單次耗費(fèi)時(shí)間:399ms
單次耗費(fèi)時(shí)間:384ms
單次耗費(fèi)時(shí)間:355ms
單次耗費(fèi)時(shí)間:353ms
單次耗費(fèi)時(shí)間:363ms
... ...
單次耗費(fèi)時(shí)間:361ms
單次耗費(fèi)時(shí)間:356ms
單次耗費(fèi)時(shí)間:355ms
單次耗費(fèi)時(shí)間:357ms
單次耗費(fèi)時(shí)間:351ms
單次耗費(fèi)時(shí)間:354ms
平均耗費(fèi)時(shí)間:366ms
366ms。fastjson2結(jié)果
單次耗費(fèi)時(shí)間:957ms
單次耗費(fèi)時(shí)間:803ms
單次耗費(fèi)時(shí)間:468ms
單次耗費(fèi)時(shí)間:435ms
單次耗費(fèi)時(shí)間:622ms
單次耗費(fèi)時(shí)間:409ms
單次耗費(fèi)時(shí)間:430ms
··· ···
單次耗費(fèi)時(shí)間:400ms
單次耗費(fèi)時(shí)間:641ms
單次耗費(fèi)時(shí)間:403ms
單次耗費(fèi)時(shí)間:398ms
單次耗費(fèi)時(shí)間:431ms
單次耗費(fèi)時(shí)間:356ms
單次耗費(fèi)時(shí)間:362ms
單次耗費(fèi)時(shí)間:626ms
單次耗費(fèi)時(shí)間:404ms
單次耗費(fèi)時(shí)間:395ms
平均耗費(fèi)時(shí)間:478ms
478ms,反而比fastjson還要慢。2.2 fastjson2慢的原因?


GC次數(shù)

Full GC

2.3 fastjson的GC表現(xiàn)
GC次數(shù)

(478?100?366?100)/1000 =11.2(478*100 - 366*100)/1000 = 11.2
2.4 第二次試驗(yàn)
-Xms64m -Xmx64m
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3210)
at java.util.Arrays.copyOf(Arrays.java:3181)
at java.util.ArrayList.grow(ArrayList.java:265)
at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:239)
at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:231)
at java.util.ArrayList.add(ArrayList.java:462)
at com.alibaba.fastjson2.JSONReader.read(JSONReader.java:1274)
at com.alibaba.fastjson2.JSON.parseArray(JSON.java:1494)
at com.alibaba.fastjson2.JSONArray.parseArray(JSONArray.java:1391)
at com.wjbgn.fastjson2.test.TestFastJson2.main(TestFastJson2.java:43)


2.5 第三次實(shí)驗(yàn)
256M。fastjson
單次耗費(fèi)時(shí)間:805ms
單次耗費(fèi)時(shí)間:224ms
單次耗費(fèi)時(shí)間:235ms
單次耗費(fèi)時(shí)間:228ms
單次耗費(fèi)時(shí)間:222ms
... ...
單次耗費(fèi)時(shí)間:191ms
單次耗費(fèi)時(shí)間:196ms
單次耗費(fèi)時(shí)間:193ms
單次耗費(fèi)時(shí)間:194ms
單次耗費(fèi)時(shí)間:192ms
平均耗費(fèi)時(shí)間:198ms
198ms。fastjson2
單次耗費(fèi)時(shí)間:671ms
單次耗費(fèi)時(shí)間:496ms
單次耗費(fèi)時(shí)間:412ms
單次耗費(fèi)時(shí)間:405ms
單次耗費(fèi)時(shí)間:315ms
單次耗費(fèi)時(shí)間:321ms
... ...
單次耗費(fèi)時(shí)間:337ms
單次耗費(fèi)時(shí)間:326ms
平均耗費(fèi)時(shí)間:335ms
335毫秒,隨著內(nèi)存增加,性能有提升,但是仍然沒有fastjson1快。2.6 第四次試驗(yàn)
1g:-Xms1g -Xmx1g
fastjson
單次耗費(fèi)時(shí)間:943ms
單次耗費(fèi)時(shí)間:252ms
單次耗費(fèi)時(shí)間:156ms
單次耗費(fèi)時(shí)間:155ms
... ...
單次耗費(fèi)時(shí)間:119ms
單次耗費(fèi)時(shí)間:114ms
單次耗費(fèi)時(shí)間:108ms
單次耗費(fèi)時(shí)間:133ms
單次耗費(fèi)時(shí)間:115ms
平均耗費(fèi)時(shí)間:133ms
133ms。fastjson2
單次耗費(fèi)時(shí)間:705ms
單次耗費(fèi)時(shí)間:199ms
單次耗費(fèi)時(shí)間:172ms
... ...
單次耗費(fèi)時(shí)間:101ms
單次耗費(fèi)時(shí)間:124ms
單次耗費(fèi)時(shí)間:96ms
平均耗費(fèi)時(shí)間:119ms
2.7 小結(jié)
fastjson2相比fastjson確實(shí)是有性能提升,但是取決于堆內(nèi)存的大小。
堆空間小的情況下,fastjson的性能表現(xiàn)優(yōu)于fastjson2。
在適當(dāng)?shù)那闆r先,對(duì)jvm進(jìn)行調(diào)優(yōu),是對(duì)應(yīng)用程序的性能有影響的
我們需要知道,堆空間并非越大越好,空間越大代表著GC處理時(shí)間會(huì)越長,其表現(xiàn)為應(yīng)用響應(yīng)時(shí)間的增加。
三、源碼分析
writer reader
3.1 序列化 writer
toJSONString方法
JSONObject.toJSONString的體現(xiàn),所以我們通過跟蹤其源碼去發(fā)現(xiàn)其原理,注意我寫注釋的位置。/**
* Serialize Java Object to JSON {@link String} with specified {@link JSONReader.Feature}s enabled
*
* @param object Java Object to be serialized into JSON {@link String}
* @param features features to be enabled in serialization
*/
static String toJSONString(Object object, JSONWriter.Feature... features) {
// 初始化 【ObjectWriterProvider】 ,關(guān)注【JSONFactory.defaultObjectWriterProvider】
JSONWriter.Context writeContext = new JSONWriter.Context(JSONFactory.defaultObjectWriterProvider, features);
boolean pretty = (writeContext.features & JSONWriter.Feature.PrettyFormat.mask) != 0;
// 初始化jsonwriter,ObjectWriter會(huì)將json數(shù)據(jù)寫入jsonwriter
JSONWriterUTF16 jsonWriter = JDKUtils.JVM_VERSION == 8 ? new JSONWriterUTF16JDK8(writeContext) : new JSONWriterUTF16(writeContext);
try (JSONWriter writer = pretty ?
new JSONWriterPretty(jsonWriter) : jsonWriter) {
if (object == null) {
writer.writeNull();
} else {
writer.setRootObject(object);
Class<?> valueClass = object.getClass();
boolean fieldBased = (writeContext.features & JSONWriter.Feature.FieldBased.mask) != 0;
// 獲取ObjectWriter
ObjectWriter<?> objectWriter = writeContext.provider.getObjectWriter(valueClass, valueClass, fieldBased);
// ObjectWriter將數(shù)據(jù)寫入JSONWriter
objectWriter.write(writer, object, null, null, 0);
}
return writer.toString();
}
}
defaultObjectWriterProvider對(duì)象
JSONFactory.defaultObjectWriterProvider的內(nèi)容:public ObjectWriterProvider() {
init();
// 初始化【ObjectWriterCreator】,用來創(chuàng)建【ObjectWriterProvider】
ObjectWriterCreator creator = null;
switch (JSONFactory.CREATOR) {
case "reflect": //反射
creator = ObjectWriterCreator.INSTANCE;
break;
case "lambda": // lambda
creator = ObjectWriterCreatorLambda.INSTANCE;
break;
case "asm":
default:
try {//asm
creator = ObjectWriterCreatorASM.INSTANCE;
} catch (Throwable ignored) {
// ignored
}
if (creator == null) {
creator = ObjectWriterCreatorLambda.INSTANCE;
}
break;
}
this.creator = creator;
}
ObjectWriterCreator,其實(shí)現(xiàn)方式默認(rèn)是「基于ASM的動(dòng)態(tài)字節(jié)碼」實(shí)現(xiàn)。反射 和 lambda 的方式。ObjectWriterProvider,它的作用是用來獲取ObjectWriter的。getObjectWriter方法
ObjectWriter的作用就是將java對(duì)象寫入到j(luò)son當(dāng)中,所以我們下面開始關(guān)注這一行代碼的實(shí)現(xiàn):writeContext.provider.getObjectWriter(valueClass, valueClass, fieldBased);
getObjectWriter方法,查看關(guān)鍵位置代碼:if (objectWriter == null) {
// 獲取creator,此處獲取的是方法開始時(shí)默認(rèn)的【ObjectWriterCreatorASM】
ObjectWriterCreator creator = getCreator();
if (objectClass == null) {
objectClass = TypeUtils.getMapping(objectType);
}
// 此處創(chuàng)建ObjectWriter,內(nèi)部創(chuàng)建【FieldWriter】
objectWriter = creator.createObjectWriter(
objectClass,
fieldBased ? JSONWriter.Feature.FieldBased.mask : 0,
modules
);
ObjectWriter previous = fieldBased
? cacheFieldBased.putIfAbsent(objectType, objectWriter)
: cache.putIfAbsent(objectType, objectWriter);
if (previous != null) {
objectWriter = previous;
}
}
createObjectWriter方法
// 遍歷java對(duì)象當(dāng)中的getter方法,獲取屬性名
BeanUtils.getters(objectClass, method -> {
... ...
String fieldName;
if (fieldInfo.fieldName == null || fieldInfo.fieldName.isEmpty()) {
if (record) {
fieldName = method.getName();
} else {
// 根據(jù)getter獲取到屬性名稱
fieldName = BeanUtils.getterName(method.getName(), beanInfo.namingStrategy);
}
} else {
fieldName = fieldInfo.fieldName;
}
... ...
//創(chuàng)建該屬性的fieldWriter
FieldWriter fieldWriter = createFieldWriter(
objectClass,
fieldName,
fieldInfo.ordinal,
fieldInfo.features,
fieldInfo.format,
fieldInfo.label,
method,
writeUsingWriter
);
// 將屬性名作為key,fieldWriter作為value放入緩存【fieldWriterMap】
FieldWriter origin = fieldWriterMap.putIfAbsent(fieldName, fieldWriter);
fieldWriters = new ArrayList<>(fieldWriterMap.values());

//定義【ObjectWriter_1】的屬性
genFields(fieldWriters, cw);
// 定義【ObjectWriter_1】的方法
genMethodInit(fieldWriters, cw, classNameType);
//定義【ObjectWriter_1】獲取對(duì)象屬性的讀取方法
genGetFieldReader(
fieldWriters,
cw,
classNameType,
new ObjectWriterAdapter(objectClass, null, null, features, fieldWriters)
);
genMethodWrite(objectClass, fieldWriters, cw, classNameType, writerFeatures);
write方法,并匹配當(dāng)前java對(duì)象的屬性屬于哪種類型,使用哪種FieldWriter進(jìn)行寫入。String和Integer,如下:... ...
else if (fieldClass == Integer.class) {
// 處理Integer屬性
gwInt32(mwc, fieldWriter, OBJECT, i);
} else if (fieldClass == String.class) {
// 處理String屬性
gwFieldValueString(mwc, fieldWriter, OBJECT, i);
}
... ...
Integer 在內(nèi)部處理時(shí),會(huì)在動(dòng)態(tài)對(duì)象生成名稱是
writeInt32的方法。String 內(nèi)部處理時(shí)在動(dòng)態(tài)對(duì)象生成方法
writeString。
genMethodWriteArrayMapping("writeArrayMapping", objectClass, writerFeatures, fieldWriters, cw, classNameType);
String
else if (fieldClass == String.class) {
methodName = "writeString";
methodDesc = "(Ljava/lang/String;)V";
}
Integer 則是對(duì)象 "(Ljava/lang/Object;)V"
ObjectWriter_1對(duì)象就設(shè)置完成了,使用反射進(jìn)行創(chuàng)建:try {
Constructor<?> constructor = deserClass.getConstructor(Class.class, String.class, String.class, long.class, List.class);
return (ObjectWriter) constructor.newInstance(objectClass, beanInfo.typeKey, beanInfo.typeName, writerFeatures, fieldWriters);
} catch (Throwable e) {
throw new JSONException("create objectWriter error, objectType " + objectClass, e);
}
回到toJSONString方法

objectWriter.write(writer, object, null, null, 0);
String:我們雖然提到過使用的
writeString方法,但是你會(huì)發(fā)現(xiàn)沒有對(duì)應(yīng)的FieldWriter,因?yàn)樗褂玫氖?code style="font-size: 14px;word-wrap: break-word;margin: 0 2px;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: #3594F7;background: RGBA(59, 170, 250, .1);padding: 0 2px;border-radius: 2px;height: 21px;line-height: 22px;">JSONWriterUTF16JDK8的writeString(String str)方法,不同版本的jdk有不同的Class。Integr:使用
FieldWriterInt32的writeInt32(JSONWriter jsonWriter, int value)進(jìn)行寫入。
小結(jié)

ObjectWriter對(duì)象,針對(duì)不同的對(duì)象屬性,生成不同的寫入方法,最終通過反射進(jìn)行對(duì)象創(chuàng)建,最后進(jìn)行java對(duì)象數(shù)據(jù)的寫入?!?/strong>3.2 反序列化 reader
parseObject 方法
/**
* json轉(zhuǎn)換java對(duì)象
*
* @param text json字符串
* @param 需要轉(zhuǎn)換的類
* @return Class
*/
@SuppressWarnings("unchecked")
static <T> T parseObject(String text, Class<T> clazz) {
if (text == null || text.isEmpty()) {
return null;
}
//創(chuàng)建reader,內(nèi)部與writer相同,使用ASM動(dòng)態(tài)字節(jié)碼形式創(chuàng)建creater
try (JSONReader reader = JSONReader.of(text)) {
// 獲取上下文
JSONReader.Context context = reader.context;
boolean fieldBased = (context.features & JSONReader.Feature.FieldBased.mask) != 0;
// 獲取ObjectReader
ObjectReader<T> objectReader = context.provider.getObjectReader(clazz, fieldBased);
T object = objectReader.readObject(reader, 0);
if (reader.resolveTasks != null) {
reader.handleResolveTasks(object);
}
return object;
}
}
JSONReader.of方法
public static JSONReader of(String str) {
if (str == null) {
throw new NullPointerException();
}
//創(chuàng)建reader的上下文,內(nèi)部與writer相同,使用ASM動(dòng)態(tài)字節(jié)碼形式創(chuàng)建creater,包裝成context
Context context = JSONFactory.createReadContext();
// jdk8以上版本使用下面的字符串處理方式
if (JDKUtils.JVM_VERSION > 8 && JDKUtils.UNSAFE_SUPPORT && str.length() > 1024 * 1024) {
try {
byte coder = UnsafeUtils.getStringCoder(str);
if (coder == 0) {
byte[] bytes = UnsafeUtils.getStringValue(str);
return new JSONReaderASCII(context, str, bytes, 0, bytes.length);
}
} catch (Exception e) {
throw new JSONException("unsafe get String.coder error");
}
return new JSONReaderStr(context, str, 0, str.length());
}
// jdk 8 及以下字符串處理
final int length = str.length();
char[] chars;
if (JDKUtils.JVM_VERSION == 8) {
// jdk8字符串轉(zhuǎn)char
chars = JDKUtils.getCharArray(str);
} else {
chars = str.toCharArray();
}
// 創(chuàng)建JSONReaderUTF16對(duì)象
return new JSONReaderUTF16(context, str, chars, 0, length);
}
getObjectReader方法
if (objectReader == null) {
// 獲取前面創(chuàng)建的creater
ObjectReaderCreator creator = getCreator();
// 創(chuàng)建ObjectReader對(duì)象,根據(jù)java類的類型
objectReader = creator.createObjectReader(objectClass, objectType, fieldBased, modules);
}
createObjectReader方法
// 創(chuàng)建屬性讀取對(duì)象數(shù)組
FieldReader[] fieldReaderArray = createFieldReaders(objectClass, objectType, beanInfo, fieldBased, modules);
BeanUtils.setters(objectClass, method -> {
fieldInfo.init();
// 創(chuàng)建Fieldreader
createFieldReader(objectClass, objectType, namingStrategy, orders, fieldInfo, method, fieldReaders, modules);
});

setterFieldReaders,用于向java對(duì)象寫入數(shù)據(jù)。回到parseObject
object = objectReader.readObject(reader, 0);
fieldReaders,它內(nèi)部包含json當(dāng)中的屬性和對(duì)象的set方法:
valueMap.put(fieldReader.getFieldNameHash(), fieldValue);
T object = createInstanceNoneDefaultConstructor(
valueMap == null
? Collections.emptyMap()
: valueMap);
return (T) constructor.newInstance(args);
小結(jié)

四、總結(jié)
fastjson2對(duì)于fastjson的兼容,可以使用下面的依賴:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.8</version>
</dependency>
內(nèi)存占用,通過前面的測試,發(fā)現(xiàn)fastjson2有明顯占用更大內(nèi)存的現(xiàn)象,甚至在相同內(nèi)存條件下,fastjson1可以完美執(zhí)行,而fastjson2有產(chǎn)生「內(nèi)存溢出」的風(fēng)險(xiǎn)。
Issues
https://github.com/alibaba/fastjson2/issues通過官方的Issues能夠發(fā)現(xiàn)目前的bug還是比較多的,對(duì)于需要穩(wěn)定性的項(xiàng)目還是不建議嘗試。具體表現(xiàn)如下:

源碼閱讀難度,這個(gè)是我最想吐槽的,全部源碼幾乎沒有注釋信息,讀起來還是比較晦澀的。作者希望讀者能夠通過PR的方式補(bǔ)充注釋,也希望更多讀者加入進(jìn)來,目前關(guān)于Fastjson2的源碼閱讀文章基本為0。

https://alibaba.github.io/fastjson2/benchmark_cn可以看得出來,感興趣可以本地實(shí)測一下。往期熱門文章:
1、計(jì)算機(jī)專業(yè)會(huì)不會(huì)成為下一個(gè)土木? 2、xxl-job驚艷的設(shè)計(jì),怎能叫人不愛 3、ArrayList#subList這四個(gè)坑,一不小心就中招 4、面試官:大量請(qǐng)求 Redis 不存在的數(shù)據(jù),從而影響數(shù)據(jù)庫,該如何解決? 5、MySQL 暴跌! 6、超越 Xshell!號(hào)稱下一代 Terminal 終端神器,用完愛不釋手! 7、IDEA 官宣全新默認(rèn) UI,太震撼了??! 8、讓你直呼「臥槽」的 GitHub 項(xiàng)目! 9、Kafka又笨又重,為啥不選Redis? 10、50多個(gè)高頻免費(fèi) API 接口分享
