從jvm代碼優(yōu)化角度談擼代碼

1.前言

以編譯程序執(zhí)行本地代碼,比解釋執(zhí)行更快岖妄,除虛擬機(jī)解釋執(zhí)行字節(jié)碼額外消耗時(shí)間的原因之外,另一個(gè)很重要原因就是虛擬機(jī)設(shè)計(jì)團(tuán)隊(duì)幾乎把對代碼的所有優(yōu)化措施都集中在即時(shí)編譯器中藐俺。一般即時(shí)編譯器生成的本地代碼比javac產(chǎn)生的字節(jié)碼更優(yōu)秀兑燥。

HotSpot虛擬機(jī)在即時(shí)編譯器中采用的優(yōu)化技術(shù):https://wiki.openjdk.java.net/display/HotSpot/PerformanceTacticIndex

下面談一下jit編譯優(yōu)化的常見手段。

2 方法內(nèi)聯(lián)

編譯器最總要的優(yōu)化是方法內(nèi)聯(lián)琴锭。遵循面向?qū)ο蠓绞骄帉懙拇a晰甚,通常會有很多get,set方法决帖。

優(yōu)化前代碼:
static class B{
int value;
final int get(){
return value;
}
}

public void foo(){
y = b.get();
z = b.get();
sum = y + z;
}

內(nèi)聯(lián)是最重要的優(yōu)化措施厕九,原因有二。一地回,降低調(diào)用的開銷(如建立棧幀等)扁远;二,方法內(nèi)聯(lián)為其他優(yōu)化措施建立良好基礎(chǔ)刻像,方法內(nèi)聯(lián)膨脹后畅买,可在更大范圍采用后續(xù)優(yōu)化手段。

內(nèi)聯(lián)后代碼:
public void foo(){
y = b.value;
z = b.value;
sum = y + z;
}

第二步優(yōu)化:冗余訪問消除
y和z的表達(dá)式一樣细睡,所以可以不必去訪問對象b谷羞,替換為z=y。如果把b.value視為表達(dá)式溜徙,則該優(yōu)化也可以理解為公共表達(dá)式消除湃缎。

冗余存儲消除后代碼:
public void foo(){
y = b.value;
z = y;
sum = y + z;
}

第三步優(yōu)化:復(fù)寫傳播
z與y的值一樣,不必使用額外的變量蠢壹,用y代替z

復(fù)寫傳播優(yōu)化后:
public void foo(){
y = b.value;
y = y;
sum = y + y;
}

第四步優(yōu)化:無用代碼消除

public void foo(){
y = b.value;
sum = y + y;
}

經(jīng)過優(yōu)化以后嗓违,作用一樣,但是省略了很多代碼(從字節(jié)碼知残,機(jī)器碼指令上的差距更大)靠瞎,執(zhí)行效率更高比庄。

由上面的例子,我們可以看到乏盐,方法內(nèi)聯(lián)是很多優(yōu)化手段的基礎(chǔ)佳窑。方法是否內(nèi)聯(lián),取決于方法是否夠熱(jvm根據(jù)內(nèi)部方法判斷父能,如是否調(diào)用頻繁)神凑,以及大小。只有方法夠熱何吝,并且字節(jié)碼小于325字節(jié)溉委,或者方法小于35字節(jié)時(shí)才能內(nèi)聯(lián)。

由此我們可以和平時(shí)擼代碼的一個(gè)小經(jīng)驗(yàn)印證起來爱榕,多寫小方法瓣喊,把職責(zé)完整的邏輯段抽取出來,成為獨(dú)立的小方法黔酥。小方法好處有幾個(gè):
一 邏輯簡單藻三,職責(zé)單一,與設(shè)計(jì)原則的單一性對應(yīng)跪者;
二 抽出去小方法棵帽,類似的,職責(zé)上接近的渣玲,可以當(dāng)成下一步抽象優(yōu)化的基礎(chǔ)逗概;
三 小方法更容易滿足內(nèi)聯(lián)條件。

3 逃逸分析

逃逸分析與方法內(nèi)聯(lián)相似忘衍,也是其他優(yōu)化手段的基礎(chǔ)逾苫。
逃逸分析的行為就是分心對象動(dòng)態(tài)作用域,當(dāng)一個(gè)對象不會逃逸到方法或者線程之外淑履,也就是其他方法或者線程無法通過任何路徑訪問到該對象時(shí)隶垮,則可進(jìn)行一些高效優(yōu)化。

棧上分配:堆中分配的對象秘噪,無論是驗(yàn)證,整理或者清理都需耗費(fèi)時(shí)間勉耀,如果確定對象不會逃逸指煎,則可在棧上分配內(nèi)存,對象所在的內(nèi)存就可以隨棧幀出棧而銷毀便斥。在一般應(yīng)用中至壤,不會逃逸的局部對象比例很大,棧上分配可減少垃圾回收壓力枢纠。

同步消除:線程同步耗時(shí)很大像街,不會逃逸的對象,讀寫不會有競爭,相應(yīng)的同步措施可以消除镰绎。

標(biāo)量替換:標(biāo)量是指無法再分解的數(shù)據(jù)脓斩。java虛擬機(jī)中的原始數(shù)據(jù)類型都可以稱為標(biāo)量。如果可以繼續(xù)分解畴栖,則稱為聚合量随静,如對象。如果把一個(gè)java對象拆散吗讶,根據(jù)程序訪問的情況燎猛,將其實(shí)用到的成員變量恢復(fù)原始類型,來訪問照皆,叫做標(biāo)量替換重绷。如果逃逸分析證明一個(gè)對象不會被外部訪問,并且可以拆解膜毁,那么程序真正執(zhí)行的時(shí)候?qū)⒑芸赡懿粍?chuàng)建這個(gè)對象昭卓,改為直接創(chuàng)建該對象的成員變量來代替。進(jìn)而直接在棧上分配和讀寫爽茴,為后續(xù)優(yōu)化打基礎(chǔ)葬凳。

棧上存儲的數(shù)據(jù),很大概率會被虛擬機(jī)分配到物理機(jī)的高速寄存器中室奏。

可以看到火焰,逃逸分析也是jit優(yōu)化代碼的重要措施,那擼代碼時(shí)有啥常識可以印證胧沫?
答案:廣義的迪米特法則昌简。控制對象之間的信息流量绒怨,流向纯赎,影響。內(nèi)部實(shí)現(xiàn)和外部接口隔離南蹂。嚴(yán)格控制對象的作用域犬金,訪問權(quán)限等,使對象六剥,方法的影響最小晚顷,不但優(yōu)雅,而且更合編譯器口味疗疟。(全局變量自然不滿足逃逸分析)

4 再談對象作用域

前面都在聊jit该默,我們回到編譯方式執(zhí)行程序的場景(代碼不夠熱,解釋器執(zhí)行)策彤∷ㄐ洌看看下面這段代碼匣摘。

public static void main (String[] args)(){
{
byte[] a = new byte[64 * 1024 * 1024];
}
System.gc();
}

jvm 加上運(yùn)行參數(shù)"-verbose:gc",查看運(yùn)行日志

[GC 66846K->65888K(125632K), 0.0009397 secs]
[Full GC 65888K->65746K(125632K), 0.0051574 secs]

內(nèi)存居然沒被回收裹刮。改一下代碼
public static void main (String[] args)(){
{
byte[] a = new byte[64 * 1024 * 1024];
}
int b = 0;
System.gc();
}

再執(zhí)行一次音榜,回收了。必指。囊咏。
[GC 66401K->65778K(125632K), 0.0035471 secs]
[Full GC 65778K->218K(125632K), 0.0140596 secs]

原因在于局部變量表。局部變量表是一組變量值存儲空間塔橡,用于存放方法參數(shù)和方法內(nèi)部定義的局部變量梅割。在第一段代碼中,雖然變量離開了作用域葛家,但是在此之后户辞,沒有其他局部變量表的讀寫,GC Roots一部分的局部變量表仍然保存著對它的關(guān)聯(lián)癞谒。正常情況下沒影響底燎,但如果遇到一些特殊情況(對象占用內(nèi)存大,此方法的棧幀長時(shí)間不能被回收弹砚,方法調(diào)用次數(shù)達(dá)不到j(luò)it編譯條件)双仍,則會采用手動(dòng)set為null的方式(用來代替那句b = 0,把局部變量表清空)桌吃。

然而有一個(gè)問題朱沃,解釋執(zhí)行和jit編譯后執(zhí)行是完全不同的代碼,這里用來救命的 set null茅诱,在jit編譯后逗物,很大可能會當(dāng)成無用代碼消除,jit編譯后瑟俭,第一段代碼可以正常釋放內(nèi)存翎卓。更優(yōu)雅的方式應(yīng)當(dāng)還是嚴(yán)格控制對象作用域,減少對set null的依賴摆寄。

5 總結(jié)
寫小方法
遵循廣義迪米特法則
嚴(yán)格控制對象作用域

6 參考
深入理解java虛擬機(jī)
java性能權(quán)威指南

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末失暴,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子微饥,更是在濱河造成了極大的恐慌锐帜,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件畜号,死亡現(xiàn)場離奇詭異,居然都是意外死亡允瞧,警方通過查閱死者的電腦和手機(jī)简软,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進(jìn)店門蛮拔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人痹升,你說我怎么就攤上這事建炫。” “怎么了疼蛾?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵肛跌,是天一觀的道長。 經(jīng)常有香客問我察郁,道長衍慎,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任皮钠,我火速辦了婚禮稳捆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘麦轰。我一直安慰自己乔夯,他們只是感情好司抱,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布他匪。 她就那樣靜靜地躺著,像睡著了一般勃救。 火紅的嫁衣襯著肌膚如雪新锈。 梳的紋絲不亂的頭發(fā)上甲脏,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天,我揣著相機(jī)與錄音壕鹉,去河邊找鬼剃幌。 笑死,一個(gè)胖子當(dāng)著我的面吹牛晾浴,可吹牛的內(nèi)容都是我干的负乡。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼脊凰,長吁一口氣:“原來是場噩夢啊……” “哼抖棘!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起狸涌,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤切省,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后帕胆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體朝捆,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年懒豹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了芙盘。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片驯用。...
    茶點(diǎn)故事閱讀 39,711評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖儒老,靈堂內(nèi)的尸體忽然破棺而出蝴乔,到底是詐尸還是另有隱情,我是刑警寧澤驮樊,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布薇正,位于F島的核電站,受9級特大地震影響囚衔,放射性物質(zhì)發(fā)生泄漏挖腰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一佳魔、第九天 我趴在偏房一處隱蔽的房頂上張望曙聂。 院中可真熱鬧,春花似錦鞠鲜、人聲如沸宁脊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽榆苞。三九已至,卻和暖如春霞捡,著一層夾襖步出監(jiān)牢的瞬間坐漏,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工碧信, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留赊琳,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓砰碴,卻偏偏與公主長得像躏筏,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子呈枉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評論 2 353

推薦閱讀更多精彩內(nèi)容