JVM進(jìn)階教程之字段訪問優(yōu)化淺析
前言
在實(shí)際中,Java程序中的對象或許 本身就是逃逸 的,或許因?yàn)?方法內(nèi)聯(lián)不夠徹底 而被即時編譯器 當(dāng)成是逃逸 的,這兩種情況都將
導(dǎo)致即時編譯器 無法進(jìn)行標(biāo)量替換 ,這時,針對對象字段訪問的優(yōu)化顯得更為重要。
static int bar(Foo o, int x) { o.a = x; return o.a; }
- 對象o是傳入?yún)?shù), 不屬于逃逸分析的范圍 (JVM中的逃逸分析針對的是 新建對象 )
- 該方法會將所傳入的int型參數(shù)x的值存儲至實(shí)例字段Foo.a中,然后再讀取并返回同一字段的值
- 這段代碼涉及 兩次 內(nèi)存訪問操作:存儲和讀取實(shí)例字段Foo.a
- 代碼可以手工優(yōu)化成如下
static int bar(Foo o, int x) { o.a = x; return x; }
即時編譯器也能作出類似的 自動優(yōu)化
字段讀取優(yōu)化
即時編譯器會優(yōu)化 實(shí)例字段 和 靜態(tài)字段 的訪問,以 減少總的內(nèi)存訪問次數(shù)
即時編譯器將 沿著控制流 ,緩存各個字段 存儲節(jié)點(diǎn) 將要存儲的值,或者字段 讀取節(jié)點(diǎn) 所得到的值
- 當(dāng)即時編譯器 遇到對同一字段的讀取節(jié)點(diǎn) 時,如果緩存值還沒有失效,那么將讀取節(jié)點(diǎn) 替換 為該緩存值
- 當(dāng)即時編譯器 遇到對同一字段的存儲節(jié)點(diǎn) 時,會 更新 所緩存的值
- 當(dāng)即時編譯器遇到 可能更新 字段的節(jié)點(diǎn)時,它會采取 保守 的策略, 舍棄所有的緩存值
- 方法調(diào)用節(jié)點(diǎn) :在即時編譯器看來,方法調(diào)用會執(zhí)行 未知代碼
- 內(nèi)存屏障節(jié)點(diǎn) :其他線程可能異步更新了字段
樣例1
static int bar(Foo o, int x) { int y = o.a + x; return o.a + y; }
實(shí)例字段Foo.a被讀取兩次,即時編譯器會將第一次讀取的值緩存起來,并且 替換 第二次的字段讀取操作,以 節(jié)省 一次內(nèi)存訪問
static int bar(Foo o, int x) { int t = o.a; int y = t + x; return t + y; }
樣例2
static int bar(Foo o, int x) { o.a = 1; if (o.a >= 0) return x; else return -x; }
字段讀取節(jié)點(diǎn)被替換成一個 常量 ,進(jìn)一步觸發(fā)更多的優(yōu)化
static int bar(Foo o, int x) { o.a = 1; return x; }
樣例3
class Foo { boolean a; void bar() { a = true; while (a) {} } void whatever() { a = false; } }
即時編譯器會將while循環(huán)中讀取實(shí)例字段a的操作 直接替換為常量true
void bar() { a = true; while (true) {} } // 生成的機(jī)器碼將陷入這一死循環(huán)中 0x066b: mov r11,QWORD PTR [r15+0x70] // 安全點(diǎn)測試 0x066f: test DWORD PTR [r11],eax // 安全點(diǎn)測試 0x0672: jmp 0x066b // while (true)
1、可以通過 volatile 關(guān)鍵字標(biāo)記實(shí)例字段a,以 強(qiáng)制 對a的讀取
2、實(shí)際上,即時編譯器將 在volatile字段訪問前后插入內(nèi)存屏障節(jié)點(diǎn)
- 這些 內(nèi)存屏障節(jié)點(diǎn) 將 阻止 即時編譯器 將屏障之前所緩存的值用于屏障之后的讀取節(jié)點(diǎn)之上
- 在X86_64平臺上,volatile字段讀取前后的內(nèi)存屏障都是no-op
- 在 即時編譯過程中的屏障節(jié)點(diǎn) ,還是會 阻止即時編譯器的字段讀取優(yōu)化
- 強(qiáng)制在循環(huán)中使用 內(nèi)存讀取指令 訪問實(shí)例字段Foo.a的最新值
3、同理, 加解鎖操作同樣也會阻止即時編譯器的字段讀取優(yōu)化
字段存儲優(yōu)化
如果一個字段先后被存儲了兩次,而且這 兩次存儲之間沒有對第一次存儲內(nèi)容讀取 ,那么即時編譯器將 消除 第一個字段存儲
樣例1
class Foo { int a = 0; void bar() { a = 1; a = 2; } }
即時編譯器將消除bar方法的冗余存儲
void bar() { a = 2; }
樣例2
即便在某個字段的兩個存儲操作之間讀取該字段,即時編譯器也可能在 字段讀取優(yōu)化 的幫助下,將第一個存儲操作當(dāng)作 冗余存儲
場景:例如兩個存儲操作之間隔著許多代碼,又或者因?yàn)?方法內(nèi)聯(lián) 的原因,將兩個存儲操作納入到同一編譯單元里(如構(gòu)造器中字段的初始化以及隨后的更新)
class Foo { int a = 0; void bar() { a = 1; int t = a; a = t + 2; } } // 優(yōu)化為 class Foo { int a = 0; void bar() { a = 1; int t = 1; a = t + 2; } } // 進(jìn)一步優(yōu)化為 class Foo { int a = 0; void bar() { a = 3; } }
如果所存儲的字段被標(biāo)記為 volatile ,那么即時編譯器也 不能消除冗余存儲
死代碼消除
樣例1
int bar(int x, int y) { int t = x*y; t = x+y; return t; }
沒有節(jié)點(diǎn)依賴于t的第一個值 x*y ,因此該乘法運(yùn)算將被消除
int bar(int x, int y) { return x+y; }
樣例2
int bar(boolean f, int x, int y) { int t = x*y; if (f) t = x+y; return t; }
部分程序路徑上有冗余存儲(f=true),該路徑上的乘法運(yùn)算將會被消除
int bar(boolean f, int x, int y) { int t; if (f) t = x+y; else t = x*y; return t; }
樣例3
int bar(int x) { if (false) return x; else return -x; }
不可達(dá)分支指的是任何程序路徑都不可達(dá)到的分支,即時編譯器將 消除不可達(dá)分支
int bar(int x) { return -x; }
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
相關(guān)文章
springboot中的springSession的存儲和獲取實(shí)現(xiàn)
這篇文章主要介紹了springboot中的springSession的存儲和獲取實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09實(shí)例講解Java的Spring框架中的AOP實(shí)現(xiàn)
這篇文章主要介紹了Java的Spring框架中的AOP實(shí)現(xiàn)實(shí)例,AOP面向切面編程其實(shí)也可以被看作是一個設(shè)計(jì)模式去規(guī)范項(xiàng)目的結(jié)構(gòu),需要的朋友可以參考下2016-04-04Spring?Boot?整合?Fisco?Bcos部署、調(diào)用區(qū)塊鏈合約的案例
本篇文章介紹?Spring?Boot?整合?Fisco?Bcos?的相關(guān)技術(shù),最最重要的技術(shù)點(diǎn),部署、調(diào)用區(qū)塊鏈合約的工程案例,本文通過流程分析給大家介紹的非常詳細(xì),需要的朋友參考下吧2022-01-01java隨機(jī)數(shù)生成具體實(shí)現(xiàn)代碼
這篇文章主要為大家分享了java隨機(jī)數(shù)生成具體實(shí)現(xiàn)代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-04-04Springboot 使用 JSR 303 對 Controller 控制層校驗(yàn)及 Service 服務(wù)層 AOP 校驗(yàn)
這篇文章主要介紹了Springboot 使用 JSR 303 對 Controller 控制層校驗(yàn)及 Service 服務(wù)層 AOP 校驗(yàn) 使用消息資源文件對消息國際化的相關(guān)知識,需要的朋友可以參考下2017-12-12