Fastjson2你開始使用了嗎?來看看原始碼解析
highlight: a11y-dark theme: devui-blue
概述
FastJson2
是FastJson
專案的重要升級,目標是為下一個十年提供一個高效能的JSON
庫。根據官方給出的效能來看,相比v1版本,確實有了很大的提升,本篇文章我們來看下究竟做了哪些事情,使得效能有了大幅度的提升。
本篇將採用程式碼測試
+ 原始碼閱讀
的方式對FastJson2的效能提升做一個較為全面的探索。
一、環境準備
首先,我們搭建一套用於測試的環境,這裡採用springboot專案,分別建立兩個module:fastjson
和fastjson2
。使用兩個版本進行對比試驗。
程式碼結構如下所示:
1.1 引入對應依賴
在父pom當中引入一些我們需要使用的公共依賴,這裡為了簡便,使用了
xml
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
在fastjson當中引入fastjson的依賴:
xml
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.79</version>
</dependency>
在fastjson2當中引入fastjson2的依賴:
xml
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.8</version>
</dependency>
1.2 建立測試類
這裡為了方便,直接使用main方法進行測試。
-
建立類:Student.java ```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方法:
```java /* * 定義迴圈次數 / private final static Integer NUM = 100;
public static void main(String[] args) { // 總時間 long totalTime = 0L; //初始化學生資料 List
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"); } `` 上述程式碼在fastjson和fastjson2的測試中基本相同,唯一不同在於在fastjson2當中,
jsonArray.toJavaList方法轉變成了
jsonArray.toList`。
二、效能測試
本節將使用上面的程式碼進行測試。在此之前,我們首先需要針對兩個子工程設定相同的堆空間大小128M
,以免造成偏差:
2.1 第一次測試
下面正是開始測試:
-
fastjson結果
console 單次耗費時間:863ms 單次耗費時間:444ms 單次耗費時間:424ms 單次耗費時間:399ms 單次耗費時間:384ms 單次耗費時間:355ms 單次耗費時間:353ms 單次耗費時間:363ms ... ... 單次耗費時間:361ms 單次耗費時間:356ms 單次耗費時間:355ms 單次耗費時間:357ms 單次耗費時間:351ms 單次耗費時間:354ms 平均耗費時間:366ms
如上所示,除了第一次很慢,第二次變快,到最後基本穩定在360毫秒左右,最終的平均耗時是366ms
。 -
fastjson2結果
console 單次耗費時間:957ms 單次耗費時間:803ms 單次耗費時間:468ms 單次耗費時間:435ms 單次耗費時間:622ms 單次耗費時間:409ms 單次耗費時間:430ms ··· ··· 單次耗費時間:400ms 單次耗費時間:641ms 單次耗費時間:403ms 單次耗費時間:398ms 單次耗費時間:431ms 單次耗費時間:356ms 單次耗費時間:362ms 單次耗費時間:626ms 單次耗費時間:404ms 單次耗費時間:395ms 平均耗費時間:478ms
如上所示,首次執行慢,逐步變快,但是後面就出現問題了,怎麼執行的時間這麼不穩定?跨度從390多到640多?這是怎麼回事?平均時間也達到了478ms
,反而比fastjson還要慢。
2.2 fastjson2慢的原因?
比較熟悉java的應該都能想到一個問題:由於堆空間大小不夠,導致頻繁發生GC,最終導致處理時間增長?
帶著這個推測,我們使用jvisualVM來看下在fastjson2執行時,記憶體的使用情況,使用如下方式啟動:
如上所示的啟動放肆會直接開啟jvisualvm的控制面板,選擇Visual GC,最終結果如下所示:
如上所示有幾處重點,單獨看下:
-
GC次數
如上所示,總共GC了1814次,耗時34.089s,最後一次失敗的原因是記憶體分配失敗。 * Full GC
如上所示,老年代發生了316次GC,耗時27.225s。
通過上面的觀察,基本可以確定由於GC導致了fastjson2整體處理時間變長。
2.3 fastjson的GC表現
我們可以再看下fastjson當中的gc是什麼樣的:
-
GC次數
如上可知,fastjson1中發生了1675次gc,與fastjson2相比少了139次,並且時間少了11.55s。
通過前面測試的結果,fastjson1平均時間366ms,而fastjson2是478ms,分別乘以100次,能夠得到如下的時間差:
$(478100 - 366100)/1000 = 11.2$
與gc時間差11.55相差無幾,那麼我們可以得到一個結論:**fastjson2的效能表現,與堆空間的大小相關!**
2.4 第二次試驗
我們似乎得到了一個結論,但是如何確定是fastjson2的那個方法消耗更多的記憶體空間呢?畢竟我們在測試方法中,呼叫了很多的方法。
所以我們進一步調小記憶體,看看是否會有記憶體溢位呢?
我們將記憶體調整為64M:
bash
-Xms64m -Xmx64m
執行後發現果然出現了記憶體溢位,並且明確的指出是堆空間記憶體溢位:
console
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)
通過如上的異常堆疊,發現異常出現在測試程式碼的43行:
提供debug發現最終異常出現在如下程式碼:
結論:在toJsonString方法時,發生了記憶體溢位異常。
2.5 第三次實驗
下面我們將記憶體增大,看看是否能夠提升fastjson2的效能。將堆空間大小調整為256M
。
-
fastjson
console 單次耗費時間:805ms 單次耗費時間:224ms 單次耗費時間:235ms 單次耗費時間:228ms 單次耗費時間:222ms ... ... 單次耗費時間:191ms 單次耗費時間:196ms 單次耗費時間:193ms 單次耗費時間:194ms 單次耗費時間:192ms 平均耗費時間:198ms
如上所示,發現隨著堆空間增加,fastjson1有較大的效能提升,平均時長在198ms
。 * fastjson2console 單次耗費時間:671ms 單次耗費時間:496ms 單次耗費時間:412ms 單次耗費時間:405ms 單次耗費時間:315ms 單次耗費時間:321ms ... ... 單次耗費時間:337ms 單次耗費時間:326ms 平均耗費時間:335ms
如上所示,結果在335毫秒
,隨著記憶體增加,效能有提升,但是仍然沒有fastjson1快。
通過如上的實驗,我們似乎可以得到如下的結論:在資料量較大時,fastjson的效能還要好於fastjson2!
2.6 第四次試驗
本次測試我們要給足夠大堆空間,看看這兩者的效能表現,此處將堆空間設定成1g
:
bash
-Xms1g -Xmx1g
* fastjson
console
單次耗費時間:943ms
單次耗費時間:252ms
單次耗費時間:156ms
單次耗費時間:155ms
... ...
單次耗費時間:119ms
單次耗費時間:114ms
單次耗費時間:108ms
單次耗費時間:133ms
單次耗費時間:115ms
平均耗費時間:133ms
如上所示,在足夠大的記憶體條件下,fastjson的平均時間達到了133ms
。
-
fastjson2
console 單次耗費時間:705ms 單次耗費時間:199ms 單次耗費時間:172ms ... ... 單次耗費時間:101ms 單次耗費時間:124ms 單次耗費時間:96ms 平均耗費時間:119ms
如上所示,fastjson2處理速度首次高於fastjson。
2.7 小結
通過前面的測試,我們能夠得到如下的結論:
-
fastjson2相比fastjson確實是有效能提升,但是取決於堆記憶體的大小。
-
堆空間小的情況下,fastjson的效能表現優於fastjson2。
-
在適當的情況先,對jvm進行調優,是對應用程式的效能有影響的
-
我們需要知道,堆空間並非越大越好,空間越大代表著GC處理時間會越長,其表現為應用響應時間的增加。
三、原始碼分析
本節將通過閱讀原始碼的方式簡單瞭解fastjson2的原理,主要分為兩個方面進行閱讀:
- writer
- reader
為什麼通過這兩個方面?
fastjson的核心就是將java物件序列化成json(對應writer),以及將json反序列化成java物件(對應reader)。而且其內部正是通過這樣的命名方式去實現的。
3.1 序列化 writer
toJSONString方法
其實所謂的序列化,就是JSONObject.toJSONString
的體現,所以我們通過跟蹤其原始碼去發現其原理,注意我寫註釋的位置。
```java /* * 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
的內容:
java
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當中,所以我們下面開始關注這一行程式碼的實現:
java
writeContext.provider.getObjectWriter(valueClass, valueClass, fieldBased);
繼續檢視getObjectWriter
方法,檢視關鍵位置程式碼:
```java
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方法
檢視creator.createObjectWriter虛擬碼:
java
// 遍歷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;
}
... ...
在上面的getterName方法獲取到物件的屬性名,找到屬性後,建立對應的【FieldWriter】:
```java
//建立該屬性的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);
迴圈過所有的getter方法後,會得到一個全部屬性的List<FieldWriter> fieldWriters集合:
java
fieldWriters = new ArrayList<>(fieldWriterMap.values());
```
再往後,fastjson2會組裝一個動態類:【ObjectWriter_1】,在裡面組裝能夠寫入JSONWriter的各種屬性和方法,以及get屬性獲取:
定義和初始化此物件的方法如下所示: ```java //定義【ObjectWriter_1】的屬性 genFields(fieldWriters, cw);
// 定義【ObjectWriter_1】的方法 genMethodInit(fieldWriters, cw, classNameType); //定義【ObjectWriter_1】獲取物件屬性的讀取方法 genGetFieldReader( fieldWriters, cw, classNameType, new ObjectWriterAdapter(objectClass, null, null, features, fieldWriters) ); ```
此動態物件的末尾【1】是隨數量增長的。
繼續向下跟蹤到如下方法:
java
genMethodWrite(objectClass, fieldWriters, cw, classNameType, writerFeatures);
此方法主要的作用是建立【ObjectWrite_1】的write
方法,並匹配當前java物件的屬性屬於哪種型別,使用哪種FieldWriter進行寫入。
其內部會輪詢所有的屬性進行匹配,我們的屬性主要是String
和Integer
,如下:
```java
... ...
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
。
再向下會通過以下方法修改寫入不同型別屬性的方法名稱和描述資訊等
java
genMethodWriteArrayMapping("writeArrayMapping", objectClass, writerFeatures, fieldWriters, cw, classNameType);
能夠看到,Integer和String的後續處理方法不同:
* String
java
else if (fieldClass == String.class) {
methodName = "writeString";
methodDesc = "(Ljava/lang/String;)V";
}
* Integer 則是物件"(Ljava/lang/Object;)V"
到此整個ObjectWriter_1
物件就設定完成了,使用反射進行建立:
java
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方法
至此我們已經拿到java物件的屬性,併成功建立了【ObjectWriter】:
再返回toJSonString方法當中,看看Object的後續操作
拿到的ObjectWriter呼叫其【write】方法進行資料寫入:
java
objectWriter.write(writer, object, null, null, 0);
我們已經知道不同型別屬性使用不同的FieldWriter進行寫入:
* String:我們雖然提到過使用的writeString
方法,但是你會發現沒有對應的FieldWriter
,因為它使用的是JSONWriterUTF16JDK8
的writeString(String str)
方法,不同版本的jdk有不同的Class。
- Integr:使用
FieldWriterInt32
的writeInt32(JSONWriter jsonWriter, int value)
進行寫入。
關於具體的寫入過程就不在介紹了。
小結
官方提供Writer關係圖如下:
本節主要針對主要流程進行梳理,與上圖對比存在部分未講解流程,感興趣同學參照原始碼自行閱讀。
整個過程較為複雜,簡單描述為:使用ASM動態位元組碼方式作為基礎,通過java物件的getter方法獲取物件的屬性值,構建動態ObjectWriter
物件,針對不同的物件屬性,生成不同的寫入方法,最終通過反射進行物件建立,最後進行java物件資料的寫入。
值得一提的是,ObejctWriter物件是會進行快取的,有助於效能的提升。
3.2 反序列化 reader
下面來看看反序列化reader的流程。因為大體流程與writer差不多,所以以下內容不做詳細講解了。
parseObject 方法
```java
/*
* json轉換java物件
*
* @param text json字串
* @param 需要轉換的類
* @return Class
/
@SuppressWarnings("unchecked")
static
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方法
建立reader物件, ```java 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方法
與getObjectWriter類似,獲取動態的json資料讀取物件。關注重點程式碼:
java
if (objectReader == null) {
// 獲取前面建立的creater
ObjectReaderCreator creator = getCreator();
// 建立ObjectReader物件,根據java類的型別
objectReader = creator.createObjectReader(objectClass, objectType, fieldBased, modules);
}
createObjectReader方法
關注下面這行程式碼:
java
// 建立屬性讀取物件陣列
FieldReader[] fieldReaderArray = createFieldReaders(objectClass, objectType, beanInfo, fieldBased, modules);
繼續跟進,發現遍歷java物件的setter方法,此時我們應該能夠想到,向物件設定值的時候,一定是使用的setter方法:
java
BeanUtils.setters(objectClass, method -> {
fieldInfo.init();
// 建立Fieldreader
createFieldReader(objectClass, objectType, namingStrategy, orders, fieldInfo, method, fieldReaders, modules);
});
createFieldReader方法會獲取java物件當中的屬性,以及set開頭的方法。
處理完物件的屬性和set方法後,會生成ObjectReader物件進行返回:
此物件包含setterFieldReaders
,用於向java物件寫入資料。
回到parseObject
下面看如何讀取json資料到java物件:
java
object = objectReader.readObject(reader, 0);
object內部主要是迴圈遍歷fieldReaders
,它內部包含json當中的屬性和物件的set方法:
正是通過這些屬性和set方法將json的資料放到java物件當中。
首先將物件的屬性和值放到map當中:
java
valueMap.put(fieldReader.getFieldNameHash(), fieldValue);
通過下面的方法將map轉換成java物件:
java
T object = createInstanceNoneDefaultConstructor(
valueMap == null
? Collections.emptyMap()
: valueMap);
內部通過構造器和值去建立一個新的java物件:
java
return (T) constructor.newInstance(args);
注意:因為這個原因,在java物件當中必須要有一個相應的帶有引數的構造器,否則會報錯。
到此為止就成功拿到轉換後的java物件了。
小結
官方提供的Reader關係圖:
感興趣的同學可以參考上圖的內容,結合本文提供的流程,自己跟蹤一遍原始碼。
整個過成簡單描述:底層使用ASM動態位元組碼為基礎,通過java物件的setter方法去構建動態的ObjectReader物件,最終通過構造器去建立一個新的java物件。
四、總結
關於fastjson2的簡單測試,以及原始碼閱讀到此就告一段落了。
針對fastjson2有以下幾點總結:
- fastjson2對於fastjson的相容,可以使用下面的依賴:
java <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>2.0.8</version> </dependency>
但是官方也不保證100%相容。 - 記憶體佔用,通過前面的測試,發現fastjson2有明顯佔用更大記憶體的現象,甚至在相同記憶體條件下,fastjson1可以完美執行,而fastjson2有產生記憶體溢位的風險。
-
Issues:通過官方的Issues能夠發現目前的bug還是比較多的,對於需要穩定性的專案還是不建議嘗試。具體表現如下:
* 原始碼閱讀難度,這個是我最想吐槽的,全部原始碼幾乎沒有註釋資訊,讀起來還是比較晦澀的。作者希望讀者能夠通過PR的方式補充註釋,也希望更多讀者加入進來,目前關於Fastjson2的原始碼閱讀文章基本為0。
拋開上述存在的問題,fastjson2確實有不錯的效能提升,通過官方提供的測試資料可以看得出來,感興趣可以本地實測一下。
到此為止關於fastjson2的介紹就結束了,感謝大家的觀看。
我個人也是摸索著去學習和閱讀,對於有些解釋可能還存在一些誤區和誤讀,希望愛好閱讀原始碼的朋友們幫忙指點出來。本文僅作為大家閱讀原始碼的參考,希望有更多的fastjson2的原始碼閱讀類文章出現,便於大家一起學習。
我正在參與掘金技術社群創作者簽約計劃招募活動,點選連結報名投稿。
- 多年以後【PageHelper】又深深的給我上了一課!!
- 入職東北國企做程式設計師一個月,感受如何?
- 網路程式設計(六)IP協議相關技術
- java效能優化--四種常見垃圾收集器描述(提供詳細介紹文章)
- java效能優化--分代回收入門
- 網路程式設計(五)IP協議-下
- 一文探索【skywalking】如何通過agent實現啟動流程
- 一個人一雙手寫一個國產stackoverflow-【BSolver】
- Fastjson2你開始使用了嗎?來看看原始碼解析
- Java效能優化--編譯閾值優化
- 【Spring Cloud Tencent】開源了,倔強的我先去嚐嚐鮮!!
- java效能優化--編譯器優化進階(編譯執行緒、內聯、逃逸分析)
- java效能優化--jvm中哪些方法被編譯了?
- java效能優化--程式碼快取優化
- java效能優化--編譯器版本與平臺對應關係
- 【2022】努力生活,不負他人,不負自己
- 年輕人瘋狂逃離的東方小巴黎|出路在哪裡
- java效能分析--JDK自帶監控工具值jcmd
- 炎炎夏日去哪玩,自制天氣預報幫你選!
- Redis壓縮表、跳躍表?拿來吧你