号称引领下一个十年的高性能JSON库fastjson2登场了
Java后端技术
共 27080字,需浏览 55分钟
·
2022-07-10 17:39
往期热门文章:
1、Lombok同时使⽤@Data和@Builder 的坑 2、IntelliJ IDEA快捷键大全 + 动图演示,建议收藏! 3、如何防止你的 jar 被反编译? 4、大公司为什么禁止SpringBoot项目使用Tomcat? 5、Java 8 的 Stream 不好调试?别逗了!IDEA 调试就能轻松搞定!!
概述
FastJson2
是FastJson
项目的重要升级,目标是为下一个十年提供一个高性能的JSON
库。根据官方给出的性能来看,相比v1版本,确实有了很大的提升,本篇文章我们来看下究竟做了哪些事情,使得性能有了大幅度的提升。代码测试
+ 源码阅读
的方式对FastJson2的性能提升做一个较为全面的探索。一、环境准备
fastjson
和fastjson2
。使用两个版本进行对比试验。1.1 引入对应依赖
<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 创建测试类
创建类: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;
}
}
创建测试main方法:
/**
* 定义循环次数
*/
private final static Integer NUM = 100;
public static void main(String[] args) {
// 总时间
long totalTime = 0L;
//初始化学生数据
List<Student> studentList = new ArrayList<>();
// 10w学生
for (int i = 0; i < 100000; i++) {
studentList.add(Student.builder().name("我犟不过你").age(10).address("黑龙江省哈尔滨市南方区哈尔滨大街267号").build());
}
// 按指定次数循环
for (int i = 0; i < NUM; i++) {
// 单次循环开始时间
long startTime = System.currentTimeMillis();
// 遍历学生数据
studentList.forEach(student -> {
// 序列化
String s = JSONObject.toJSONString(student);
//字符串转回java对象
JSONObject.parseObject(s, Student.class);
});
// 将学生list序列化,之后转为jsonArray
JSONArray jsonArray = JSONArray.parseArray(JSONObject.toJSONString(studentList));
// 将jsonArray转java对象list
jsonArray.toJavaList(Student.class);
//单次处理时间
long endTime = System.currentTimeMillis();
// 单次耗时
totalTime += (endTime - startTime);
System.out.println("单次耗费时间:" + (endTime - startTime) + "ms");
}
System.out.println("平均耗费时间:" + totalTime / NUM + "ms");
}
jsonArray.toJavaList
方法转变成了jsonArray.toList
。二、性能测试
128M
,以免造成偏差:2.1 第一次测试
fastjson结果
单次耗费时间:863ms
单次耗费时间:444ms
单次耗费时间:424ms
单次耗费时间:399ms
单次耗费时间:384ms
单次耗费时间:355ms
单次耗费时间:353ms
单次耗费时间:363ms
... ...
单次耗费时间:361ms
单次耗费时间:356ms
单次耗费时间:355ms
单次耗费时间:357ms
单次耗费时间:351ms
单次耗费时间:354ms
平均耗费时间:366ms
366ms
。fastjson2结果
单次耗费时间:957ms
单次耗费时间:803ms
单次耗费时间:468ms
单次耗费时间:435ms
单次耗费时间:622ms
单次耗费时间:409ms
单次耗费时间:430ms
··· ···
单次耗费时间:400ms
单次耗费时间:641ms
单次耗费时间:403ms
单次耗费时间:398ms
单次耗费时间:431ms
单次耗费时间:356ms
单次耗费时间:362ms
单次耗费时间:626ms
单次耗费时间:404ms
单次耗费时间:395ms
平均耗费时间:478ms
478ms
,反而比fastjson还要慢。2.2 fastjson2慢的原因?
GC次数
Full GC
2.3 fastjson的GC表现
GC次数
(478∗100−366∗100)/1000 =11.2(478*100 - 366*100)/1000 = 11.2
2.4 第二次试验
-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 第三次实验
256M
。fastjson
单次耗费时间:805ms
单次耗费时间:224ms
单次耗费时间:235ms
单次耗费时间:228ms
单次耗费时间:222ms
... ...
单次耗费时间:191ms
单次耗费时间:196ms
单次耗费时间:193ms
单次耗费时间:194ms
单次耗费时间:192ms
平均耗费时间:198ms
198ms
。fastjson2
单次耗费时间:671ms
单次耗费时间:496ms
单次耗费时间:412ms
单次耗费时间:405ms
单次耗费时间:315ms
单次耗费时间:321ms
... ...
单次耗费时间:337ms
单次耗费时间:326ms
平均耗费时间:335ms
335毫秒
,随着内存增加,性能有提升,但是仍然没有fastjson1快。2.6 第四次试验
1g
:-Xms1g -Xmx1g
fastjson
单次耗费时间:943ms
单次耗费时间:252ms
单次耗费时间:156ms
单次耗费时间:155ms
... ...
单次耗费时间:119ms
单次耗费时间:114ms
单次耗费时间:108ms
单次耗费时间:133ms
单次耗费时间:115ms
平均耗费时间:133ms
133ms
。fastjson2
单次耗费时间:705ms
单次耗费时间:199ms
单次耗费时间:172ms
... ...
单次耗费时间:101ms
单次耗费时间:124ms
单次耗费时间:96ms
平均耗费时间:119ms
2.7 小结
fastjson2相比fastjson确实是有性能提升,但是取决于堆内存的大小。
堆空间小的情况下,fastjson的性能表现优于fastjson2。
在适当的情况先,对jvm进行调优,是对应用程序的性能有影响的
我们需要知道,堆空间并非越大越好,空间越大代表着GC处理时间会越长,其表现为应用响应时间的增加。
三、源码分析
writer reader
3.1 序列化 writer
toJSONString方法
JSONObject.toJSONString
的体现,所以我们通过跟踪其源码去发现其原理,注意我写注释的位置。/**
* 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】 ,关注【JSONFactory.defaultObjectWriterProvider】
JSONWriter.Context writeContext = new JSONWriter.Context(JSONFactory.defaultObjectWriterProvider, features);
boolean pretty = (writeContext.features & JSONWriter.Feature.PrettyFormat.mask) != 0;
// 初始化jsonwriter,ObjectWriter会将json数据写入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将数据写入JSONWriter
objectWriter.write(writer, object, null, null, 0);
}
return writer.toString();
}
}
defaultObjectWriterProvider对象
JSONFactory.defaultObjectWriterProvider
的内容:public ObjectWriterProvider() {
init();
// 初始化【ObjectWriterCreator】,用来创建【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
,其实现方式默认是「基于ASM的动态字节码」实现。反射
和 lambda
的方式。ObjectWriterProvider
,它的作用是用来获取ObjectWriter
的。getObjectWriter方法
ObjectWriter
的作用就是将java对象写入到json当中,所以我们下面开始关注这一行代码的实现:writeContext.provider.getObjectWriter(valueClass, valueClass, fieldBased);
getObjectWriter
方法,查看关键位置代码:if (objectWriter == null) {
// 获取creator,此处获取的是方法开始时默认的【ObjectWriterCreatorASM】
ObjectWriterCreator creator = getCreator();
if (objectClass == null) {
objectClass = TypeUtils.getMapping(objectType);
}
// 此处创建ObjectWriter,内部创建【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对象当中的getter方法,获取属性名
BeanUtils.getters(objectClass, method -> {
... ...
String fieldName;
if (fieldInfo.fieldName == null || fieldInfo.fieldName.isEmpty()) {
if (record) {
fieldName = method.getName();
} else {
// 根据getter获取到属性名称
fieldName = BeanUtils.getterName(method.getName(), beanInfo.namingStrategy);
}
} else {
fieldName = fieldInfo.fieldName;
}
... ...
//创建该属性的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】获取对象属性的读取方法
genGetFieldReader(
fieldWriters,
cw,
classNameType,
new ObjectWriterAdapter(objectClass, null, null, features, fieldWriters)
);
genMethodWrite(objectClass, fieldWriters, cw, classNameType, writerFeatures);
write
方法,并匹配当前java对象的属性属于哪种类型,使用哪种FieldWriter进行写入。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 在内部处理时,会在动态对象生成名称是
writeInt32
的方法。String 内部处理时在动态对象生成方法
writeString
。
genMethodWriteArrayMapping("writeArrayMapping", objectClass, writerFeatures, fieldWriters, cw, classNameType);
String
else if (fieldClass == String.class) {
methodName = "writeString";
methodDesc = "(Ljava/lang/String;)V";
}
Integer 则是对象 "(Ljava/lang/Object;)V"
ObjectWriter_1
对象就设置完成了,使用反射进行创建: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
方法,但是你会发现没有对应的FieldWriter
,因为它使用的是JSONWriterUTF16JDK8
的writeString(String str)
方法,不同版本的jdk有不同的Class。Integr:使用
FieldWriterInt32
的writeInt32(JSONWriter jsonWriter, int value)
进行写入。
小结
ObjectWriter
对象,针对不同的对象属性,生成不同的写入方法,最终通过反射进行对象创建,最后进行java对象数据的写入。」3.2 反序列化 reader
parseObject 方法
/**
* json转换java对象
*
* @param text json字符串
* @param 需要转换的类
* @return Class
*/
@SuppressWarnings("unchecked")
static <T> T parseObject(String text, Class<T> clazz) {
if (text == null || text.isEmpty()) {
return null;
}
//创建reader,内部与writer相同,使用ASM动态字节码形式创建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();
}
//创建reader的上下文,内部与writer相同,使用ASM动态字节码形式创建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字符串转char
chars = JDKUtils.getCharArray(str);
} else {
chars = str.toCharArray();
}
// 创建JSONReaderUTF16对象
return new JSONReaderUTF16(context, str, chars, 0, length);
}
getObjectReader方法
if (objectReader == null) {
// 获取前面创建的creater
ObjectReaderCreator creator = getCreator();
// 创建ObjectReader对象,根据java类的类型
objectReader = creator.createObjectReader(objectClass, objectType, fieldBased, modules);
}
createObjectReader方法
// 创建属性读取对象数组
FieldReader[] fieldReaderArray = createFieldReaders(objectClass, objectType, beanInfo, fieldBased, modules);
BeanUtils.setters(objectClass, method -> {
fieldInfo.init();
// 创建Fieldreader
createFieldReader(objectClass, objectType, namingStrategy, orders, fieldInfo, method, fieldReaders, modules);
});
setterFieldReaders
,用于向java对象写入数据。回到parseObject
object = objectReader.readObject(reader, 0);
fieldReaders
,它内部包含json当中的属性和对象的set方法:valueMap.put(fieldReader.getFieldNameHash(), fieldValue);
T object = createInstanceNoneDefaultConstructor(
valueMap == null
? Collections.emptyMap()
: valueMap);
return (T) constructor.newInstance(args);
小结
四、总结
fastjson2对于fastjson的兼容,可以使用下面的依赖:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.8</version>
</dependency>
内存占用,通过前面的测试,发现fastjson2有明显占用更大内存的现象,甚至在相同内存条件下,fastjson1可以完美执行,而fastjson2有产生「内存溢出」的风险。
Issues
https://github.com/alibaba/fastjson2/issues
通过官方的Issues能够发现目前的bug还是比较多的,对于需要稳定性的项目还是不建议尝试。具体表现如下:
源码阅读难度,这个是我最想吐槽的,全部源码几乎没有注释信息,读起来还是比较晦涩的。作者希望读者能够通过PR的方式补充注释,也希望更多读者加入进来,目前关于Fastjson2的源码阅读文章基本为0。
https://alibaba.github.io/fastjson2/benchmark_cn
可以看得出来,感兴趣可以本地实测一下。往期热门文章:
1、计算机专业会不会成为下一个土木? 2、xxl-job惊艳的设计,怎能叫人不爱 3、ArrayList#subList这四个坑,一不小心就中招 4、面试官:大量请求 Redis 不存在的数据,从而影响数据库,该如何解决? 5、MySQL 暴跌! 6、超越 Xshell!号称下一代 Terminal 终端神器,用完爱不释手! 7、IDEA 官宣全新默认 UI,太震撼了!! 8、让你直呼「卧槽」的 GitHub 项目! 9、Kafka又笨又重,为啥不选Redis? 10、50多个高频免费 API 接口分享
评论