從HikariCP的性能優(yōu)化說起!
你知道的越多,不知道的就越多,業(yè)余的像一棵小草!
你來,我們一起精進(jìn)!你不來,我和你的競(jìng)爭(zhēng)對(duì)手一起精進(jìn)!
編輯:業(yè)余草
推薦:https://www.xttblog.com/?p=5265
你好,我是業(yè)余草,這是我的第 445 篇原創(chuàng)文章。
這篇文章我想了很久沒想到好標(biāo)題,索性就以《從HikariCP的性能優(yōu)化說起!》為題開始吧!
這兩天看到群里有人閱讀到網(wǎng)上的文章,在群里問:“invokestatic 性能比 invokevirtual 好?”
一時(shí)間難倒了不少人,有人建議去看周老師的 JVM 書籍(深入理解Java虛擬機(jī))中找答案,引起了群友廣泛的討論。不少人表示沒看過,還有部分表示看不懂。
其實(shí)不看周老師的書,也能搞定這個(gè)問題。
同時(shí),阿里巴巴出品的 Java 開發(fā)手冊(cè)中也有一段這樣的描述:
?【強(qiáng)制】避免通過一個(gè)類的對(duì)象引用訪問此類的靜態(tài)變量或靜態(tài)方法,無謂增加編譯器解析成本,直接用類名來訪問即可。
?
這句話說的太模糊了,也不給一個(gè)詳細(xì)的解釋,看的云里霧里。很多人都看懵了,也包括我自己。沒得辦法,我嘗試著從字節(jié)碼的角度找找答案,結(jié)果還真被我找到了。
我們先來看一個(gè) demo:
public class Xttblog {
public static int num = 0;
public void add(){
this.num ++;
}
}
這段代碼,如果你安裝了 p3c 插件,就會(huì)有爆紅提示:

我們先忽略這個(gè)提示,寫個(gè) main 方法,看看字節(jié)碼層面指令。
public class StaticTest {
public static void main(String[] args) {
Xttblog xttblog = new Xttblog();
xttblog.num ++;
}
}
通過類實(shí)例訪問靜態(tài)變量,阿里巴巴的規(guī)約插件同樣的會(huì)報(bào)出紅線提示。

這個(gè)提示不影響代碼運(yùn)行。我們可以執(zhí)行javap,不會(huì)用的先看java -help,或者 idea 安裝字節(jié)碼插件:jclasslib。
Compiled from "StaticTest.java"
public class com.xttblog.test.StaticTest {
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: new #2 // class com/xttblog/Xttblog
3: dup
4: invokespecial #3 // Method com/xttblog/Xttblog."<init>":()V
7: astore_1
8: aload_1
9: pop
10: getstatic #4 // Field com/xttblog/Xttblog.num:I
13: iconst_1
14: iadd
15: putstatic #4 // Field com/xttblog/Xttblog.num:I
18: return
LineNumberTable:
line 11: 0
line 12: 8
line 13: 18
LocalVariableTable:
Start Length Slot Name Signature
0 19 0 args [Ljava/lang/String;
8 11 1 xttblog Lcom/xttblog/Xttblog;
}
我們?cè)俅胃乱幌?main 方法中的代碼:
public class StaticTest {
public static void main(String[] args) {
/*Xttblog xttblog = new Xttblog();
xttblog.num++; */
Xttblog.num ++;
}
}
再一次的查看字節(jié)碼。
Compiled from "StaticTest.java"
public class com.xttblog.test.StaticTest {
public com.xttblog.test.StaticTest();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: getstatic #2 // Field com/xttblog/test/Xttblog.num:I
3: iconst_1
4: iadd
5: putstatic #2 // Field com/xttblog/test/Xttblog.num:I
8: return
你會(huì)發(fā)現(xiàn)指令變少了。除了少了創(chuàng)建對(duì)象的 new、dup、invokespecial 指令外,還多出了 astore_1、aload_1、pop 指令。
astore_1 指令:1 是代表在本地變量表(LocalVariableTable)中的序號(hào)位置。Slot 1 就是 xttblog 對(duì)象。它的意思就是,創(chuàng)建完對(duì)象后,把 xttblog 存入到本地變量表位置 1 中。
aload_1 指令:把存放在局部變量表中索引 1 位置的對(duì)象引用壓入操作棧。
pop 指令:然后,pop 指令,又把棧頂給彈出了。
到現(xiàn)在,你應(yīng)該看明白了。通過類實(shí)例去訪問靜態(tài)變量,來來回回的把 xttblog 對(duì)象給折騰了一遍。它多出了,存入本地變量表,壓棧,出棧的操作。把本來利索的事情,給多加幾道工序,反而影響運(yùn)行效率。
其他的指令我就不講了,具體可以查看我前面的文章:《JVM 常用指令速查手冊(cè),建議收藏!》。

總結(jié)一下,就是靜態(tài)變量或方法的訪問比通過類實(shí)例訪問靜態(tài)變量和方法快。同樣的道理,invokestatic 性能比 invokevirtual 好,也就不足為奇了。
實(shí)際上,HikariCP 也利用了 Javassist 來生成委托實(shí)現(xiàn)動(dòng)態(tài)代理,優(yōu)化并精簡了字節(jié)碼。

具體官方英文文檔,參考這里:https://github.com/brettwooldridge/HikariCP/wiki/Down-the-Rabbit-Hole。
HikariCP 的作者,也就是 Brett Wooldridge 大神也是通過字節(jié)碼來說明,invokestatic 性能比 invokevirtual 好,同時(shí) invokestatic 相比 invokevirtual 更容易被 JVM 優(yōu)化調(diào)用。

另外,優(yōu)化后的(上圖中的上半圖)指令,還少了一個(gè) getstatic。同時(shí)棧大小從 5 個(gè)元素減少到 4 個(gè)元素。這是因?yàn)?invokevirtual 指令在堆棧上隱式傳遞 ProxyFactory 實(shí)例的情況下(即 this),并且在調(diào)用時(shí)從堆棧中需要額外(看不見)彈出 this。
讀過周志明老師的《深入理解Java虛擬機(jī)》第二版的同學(xué),可能還知道:invokevirtual 指令的調(diào)用依賴于運(yùn)行時(shí)解析,其解析過程大致分為以下幾個(gè)步驟:
找到操作數(shù)棧頂?shù)牡谝粋€(gè)元素(本例中就是 this)所指向的對(duì)象的實(shí)際類型,記作 C。 如果在類型 C 中找到了與常量中的描述符和簡單名稱都相符的方法,則進(jìn)行訪問權(quán)限校驗(yàn),如果通過則返回這個(gè)方法的直接引用,查找過程結(jié)束;如果不通過,則返回 java.lang.IllegalAccessError 異常。 否則,按照繼承關(guān)系對(duì) C 的父類進(jìn)行第 2 步的搜索和校驗(yàn)過程。 如果始終都沒有找到合適的方法,則拋出 java.lang.AbstractMethodError 異常。
說白了,invokevirtual 會(huì)進(jìn)行查找,查找重載函數(shù),以及權(quán)限驗(yàn)證。找出類型和權(quán)限全符合的函數(shù)進(jìn)行執(zhí)行。
而 invokestatic 指令用于調(diào)用靜態(tài)方法,即使用 static 關(guān)鍵字修飾的方法;static 修飾的方法是屬于具體類的,因此不需要多余的查找和權(quán)限驗(yàn)證,因此 invokestatic 的性能比 invokevirtual 好!
時(shí)間倉促,就先聊到這里吧,如有不懂或疑問,歡迎加我微信:codedq,進(jìn)群學(xué)習(xí)!
