文章目錄
一四敞、JVM優(yōu)化
1.1 即時(shí)編譯器JIT
??類編譯加載執(zhí)行過(guò)程:
??初始化完成后,類在調(diào)用執(zhí)行過(guò)程中简识,執(zhí)行引擎會(huì)把字節(jié)碼轉(zhuǎn)為機(jī)器碼赶掖,然后在操作系統(tǒng)中才能執(zhí)行。在字節(jié)碼轉(zhuǎn)換為機(jī)器碼的過(guò)程中七扰,虛擬機(jī)中還存在著一道編譯奢赂,那就是即時(shí)編譯。
??最初颈走,虛擬機(jī)中的字節(jié)碼是由解釋器( Interpreter )完成編譯的膳灶,當(dāng)虛擬機(jī)發(fā)現(xiàn)某個(gè)方法或代碼塊的運(yùn)行特別頻繁的時(shí)候,就會(huì)把這些代碼認(rèn)定為“熱點(diǎn)代碼”。
??為了提高熱點(diǎn)代碼的執(zhí)行效率轧钓,在運(yùn)行時(shí)序厉,即時(shí)編譯器(JIT)會(huì)把這些代碼編譯成與本地平臺(tái)相關(guān)的機(jī)器碼,并進(jìn)行各層次的優(yōu)化毕箍,然后保存到內(nèi)存中弛房。
即時(shí)編譯器類型
??在 HotSpot 虛擬機(jī)中庭再,內(nèi)置了兩個(gè) JIT,分別為 C1 編譯器和 C2 編譯器常拓,這兩個(gè)編譯器的編譯過(guò)程是不一樣的。
??C1 編譯器是一個(gè)簡(jiǎn)單快速的編譯器,主要的關(guān)注點(diǎn)在于局部性的優(yōu)化店枣,適用于執(zhí)行時(shí)間較短或?qū)?dòng)性能有要求的程序,例如逾柿,GUI 應(yīng)用對(duì)界面啟動(dòng)速度就有一定要求。
??C2 編譯器是為長(zhǎng)期運(yùn)行的服務(wù)器端應(yīng)用程序做性能調(diào)優(yōu)的編譯器斥难,適用于執(zhí)行時(shí)間較長(zhǎng)或?qū)Ψ逯敌阅苡幸蟮某绦颉8鶕?jù)各自的適配性,這兩種即時(shí)編譯也被稱為 Client Compiler和 Server Compiler。
??在 Java7 之前,需要根據(jù)程序的特性來(lái)選擇對(duì)應(yīng)的 JIT纺且,虛擬機(jī)默認(rèn)采用解釋器和其中一個(gè)編譯器配合工作嫁艇。Java7 引入了分層編譯,這種方式綜合了 C1 的啟動(dòng)性能優(yōu)勢(shì)和 C2 的峰值性能優(yōu)勢(shì),可以通過(guò)參數(shù) “-client”“-server” 強(qiáng)制指定虛擬機(jī)的即時(shí)編譯模式屠橄。
??在 Java8 中,默認(rèn)開(kāi)啟分層編譯,-client 和 -server 的設(shè)置已經(jīng)是無(wú)效的了劳翰。如果只想開(kāi)啟 C2敦锌,可以關(guān)閉分層編譯(-XX:-TieredCompilation),如果只想用 C1佳簸,可以在打開(kāi)分層編譯的同時(shí)乙墙,使用參數(shù):-XX:TieredStopAtLevel=1。
??通過(guò) java -version 命令行可以直接查看到當(dāng)前系統(tǒng)使用的編譯模式生均。示例:
熱點(diǎn)探測(cè)
??在 HotSpot 虛擬機(jī)中的熱點(diǎn)探測(cè)是 JIT 優(yōu)化的條件听想,熱點(diǎn)探測(cè)是基于計(jì)數(shù)器的熱點(diǎn)探測(cè),采用這種方法的虛擬機(jī)會(huì)為每個(gè)方法建立計(jì)數(shù)器統(tǒng)計(jì)方法的執(zhí)行次數(shù)马胧,如果執(zhí)行次數(shù)超過(guò)一定的閾值就認(rèn)為它是“熱點(diǎn)方法” 汉买。
??虛擬機(jī)為每個(gè)方法準(zhǔn)備了兩類計(jì)數(shù)器:方法調(diào)用計(jì)數(shù)器(Invocation Counter)和回邊計(jì)數(shù)器(Back Edge Counter)。在確定虛擬機(jī)運(yùn)行參數(shù)的前提下佩脊,這兩個(gè)計(jì)數(shù)器都有一個(gè)確定的閾值蛙粘,當(dāng)計(jì)數(shù)器超過(guò)閾值溢出了,就會(huì)觸發(fā) JIT 編譯威彰。
方法調(diào)用計(jì)數(shù)器?
用于統(tǒng)計(jì)方法被調(diào)用的次數(shù)出牧,方法調(diào)用計(jì)數(shù)器的默認(rèn)閾值在 C1 模式下是1500 次,在 C2 模式在是 10000 次歇盼,可通過(guò) -XX: CompileThreshold 來(lái)設(shè)定舔痕;而在分層
編譯的情況下,-XX: CompileThreshold 指定的閾值將失效,此時(shí)將會(huì)根據(jù)當(dāng)前待編譯的方法數(shù)以及編譯線程數(shù)來(lái)動(dòng)態(tài)調(diào)整伯复。當(dāng)方法計(jì)數(shù)器和回邊計(jì)數(shù)器之和超過(guò)方法計(jì)數(shù)器閾值時(shí)盈咳,就會(huì)觸發(fā) JIT 編譯器。
回邊計(jì)數(shù)器?
用于統(tǒng)計(jì)一個(gè)方法中循環(huán)體代碼執(zhí)行的次數(shù)边翼,在字節(jié)碼中遇到控制流向后跳轉(zhuǎn)的指令稱為“回邊”(Back Edge)鱼响,該值用于計(jì)算是否觸發(fā) C1 編譯的閾值,在不開(kāi)啟分層編譯的情況下组底,C1 默認(rèn)為 13995丈积,C2 默認(rèn)為 10700,可通過(guò) -XX:
OnStackReplacePercentage=N 來(lái)設(shè)置债鸡;而在分層編譯的情況下江滨,-XX:
OnStackReplacePercentage 指定的閾值同樣會(huì)失效,此時(shí)將根據(jù)當(dāng)前待編譯的方法數(shù)以及編譯線程數(shù)來(lái)動(dòng)態(tài)調(diào)整厌均。
編譯優(yōu)化技術(shù)
??JIT 編譯運(yùn)用了一些經(jīng)典的編譯優(yōu)化技術(shù)來(lái)實(shí)現(xiàn)代碼的優(yōu)化唬滑,即通過(guò)一些例行檢查優(yōu)化,可以智能地編譯出運(yùn)行時(shí)的最優(yōu)性能代碼棺弊。
方法內(nèi)聯(lián)?
方法內(nèi)聯(lián)的優(yōu)化行為就是把目標(biāo)方法的代碼復(fù)制到發(fā)起調(diào)用的方法之中晶密,避免發(fā)生真實(shí)的方法調(diào)用。示例:
//原有代碼private int add1(int x1, int x2, int x3, int x4) {
returnadd2(x1,x2)+add2(x3,x4);}private int add2(int x1, int x2) {
returnx1+x2;}//優(yōu)化后代碼private int add1(int x1, int x2, int x3, int x4) {
returnx1+x2+x3+x4;}
??JVM 會(huì)自動(dòng)識(shí)別熱點(diǎn)方法模她,并對(duì)它們使用方法內(nèi)聯(lián)進(jìn)行優(yōu)化稻艰。可以通過(guò) -XX:CompileThreshold 來(lái)設(shè)置熱點(diǎn)方法的閾值侈净。但要強(qiáng)調(diào)一點(diǎn)尊勿,熱點(diǎn)方法不一定會(huì)被 JVM做內(nèi)聯(lián)優(yōu)化,如果這個(gè)方法體太大了畜侦,JVM 將不執(zhí)行內(nèi)聯(lián)操作元扔。而方法體的大小閾值,我們也可以通過(guò)參數(shù)設(shè)置來(lái)優(yōu)化:
經(jīng)常執(zhí)行的方法旋膳,默認(rèn)情況下澎语,方法體大小小于 325 字節(jié)的都會(huì)進(jìn)行內(nèi)聯(lián),我們可以通過(guò) -XX:MaxFreqInlineSize=N 來(lái)設(shè)置大小值溺忧;
不是經(jīng)常執(zhí)行的方法咏连,默認(rèn)情況下,方法大小小于 35 字節(jié)才會(huì)進(jìn)行內(nèi)聯(lián)鲁森,我們也可以通過(guò) -XX:MaxInlineSize=N 來(lái)重置大小值祟滴。
??熱點(diǎn)方法的優(yōu)化可以有效提高系統(tǒng)性能,一般可以通過(guò)以下幾種方式來(lái)提高方法內(nèi)聯(lián):
通過(guò)設(shè)置 JVM 參數(shù)來(lái)減小熱點(diǎn)閾值或增加方法體閾值歌溉,以便更多的方法可以進(jìn)行內(nèi)聯(lián)垄懂,但這種方法意味著需要占用更多地內(nèi)存骑晶;
在編程中,避免在一個(gè)方法中寫(xiě)大量代碼草慧,習(xí)慣使用小方法體桶蛔;
盡量使用 final、private漫谷、static 關(guān)鍵字修飾方法仔雷,編碼方法因?yàn)槔^承,會(huì)需要額外的類型檢查舔示。
逃逸分析
??逃逸分析(Escape Analysis)是判斷一個(gè)對(duì)象是否被外部方法引用或外部線程訪問(wèn)的分析技術(shù)碟婆,編譯器會(huì)根據(jù)逃逸分析的結(jié)果對(duì)代碼進(jìn)行優(yōu)化。
棧上分配?
在 Java 中默認(rèn)創(chuàng)建一個(gè)對(duì)象是在堆中分配內(nèi)存的惕稻,而當(dāng)堆內(nèi)存中的對(duì)象不再使用時(shí)竖共,則需要通過(guò)垃圾回收機(jī)制回收,這個(gè)過(guò)程相對(duì)分配在棧中的對(duì)象的創(chuàng)建和銷(xiāo)毀來(lái)說(shuō)俺祠,更消耗時(shí)間和性能公给。這個(gè)時(shí)候,逃逸分析如果發(fā)現(xiàn)一個(gè)對(duì)象只在方法中使用蜘渣,就會(huì)將對(duì)象分配在棧上淌铐。
標(biāo)量替換?
逃逸分析證明一個(gè)對(duì)象不會(huì)被外部訪問(wèn),如果這個(gè)對(duì)象可以被拆分的話宋梧,當(dāng)程序真正執(zhí)行的時(shí)候可能不創(chuàng)建這個(gè)對(duì)象匣沼,而直接創(chuàng)建它的成員變量來(lái)代替。將對(duì)象拆分后捂龄,可以分配對(duì)象的成員變量在棧或寄存器上加叁,原本的對(duì)象就無(wú)需分配內(nèi)存空間了倦沧。這種編譯優(yōu)化就叫做標(biāo)量替換。
總結(jié)?
可以通過(guò)設(shè)置 JVM 參數(shù)來(lái)開(kāi)關(guān)逃逸分析它匕,還可以單獨(dú)開(kāi)關(guān)同步消除和標(biāo)量替換展融,在JDK1.8 中 JVM 是默認(rèn)開(kāi)啟這些操作:
-XX:+DoEscapeAnalysis開(kāi)啟逃逸分析(jdk1.8默認(rèn)開(kāi)啟,其它版本未測(cè)試)-XX:-DoEscapeAnalysis關(guān)閉逃逸分析-XX:+EliminateLocks開(kāi)啟鎖消除(jdk1.8默認(rèn)開(kāi)啟豫柬,其它版本未測(cè)試)-XX:-EliminateLocks關(guān)閉鎖消除-XX:+EliminateAllocations開(kāi)啟標(biāo)量替換(jdk1.8默認(rèn)開(kāi)啟告希,其它版本未測(cè)試)-XX:-EliminateAllocations關(guān)閉就可以了
1.2 優(yōu)化垃圾回收機(jī)制
??可以通過(guò) JVM 工具查詢當(dāng)前 JVM 使用的垃圾收集器類型,首先通過(guò) ps 命令查詢出經(jīng)常 ID烧给,再通過(guò) jmap -heap ID 查詢出 JVM 的配置信息燕偶,其中就包括垃圾收集器的設(shè)置類型。示例:
??GC 性能衡量指標(biāo)的一些指標(biāo):
1础嫡、吞吐量?
這里的吞吐量是指應(yīng)用程序所花費(fèi)的時(shí)間和系統(tǒng)總運(yùn)行時(shí)間的比值指么。我們可以按照這個(gè)公式來(lái)計(jì)算 GC 的吞吐量:系統(tǒng)總運(yùn)行時(shí)間 = 應(yīng)用程序耗時(shí) +GC 耗時(shí)酝惧。如果系統(tǒng)運(yùn)行了 100 分鐘,GC 耗時(shí) 1 分鐘伯诬,則系統(tǒng)吞吐量為 99%晚唇。GC 的吞吐量一般不能低于95%。
2盗似、停頓時(shí)間?
指垃圾收集器正在運(yùn)行時(shí)哩陕,應(yīng)用程序的暫停時(shí)間。對(duì)于串行回收器而言赫舒,停頓時(shí)間可能會(huì)比較長(zhǎng)悍及;而使用并發(fā)回收器,由于垃圾收集器和應(yīng)用程序交替運(yùn)行号阿,程序的停頓時(shí)間就會(huì)變短并鸵,但其效率很可能不如獨(dú)占垃圾收集器,系統(tǒng)的吞吐量也很可能會(huì)降低扔涧。
3园担、垃圾回收頻率?
多久發(fā)生一次指垃圾回收呢?通常垃圾回收的頻率越低越好枯夜,增大堆內(nèi)存空間可以有效降低垃圾回收發(fā)生的頻率弯汰,但同時(shí)也意味著堆積的回收對(duì)象越多,最終也會(huì)增加回收時(shí)的停頓時(shí)間湖雹。所以我們只要適當(dāng)?shù)卦龃蠖褍?nèi)存空間咏闪,保證正常的垃圾回收頻率即可。
??查看JVM日志的幾種參數(shù)設(shè)置:
-XX:+PrintGC 輸出 GC 日志-XX:+PrintGCDetails 輸出 GC 的詳細(xì)日志-XX:+PrintGCTimeStamps 輸出 GC 的時(shí)間戳(以基準(zhǔn)時(shí)間的形式)-XX:+PrintGCDateStamps 輸出 GC 的時(shí)間戳(以日期的形式摔吏,如2013-05-04T21:53:59.234+0800)-XX:+PrintHeapAtGC 在進(jìn)行 GC 的前后打印出堆的信息-Xloggc:../logs/gc.log 日志文件的輸出路徑
??幾種常用的 GC 調(diào)優(yōu)策略:
1鸽嫂、降低 Minor GC 頻率?
通常情況下,由于新生代空間較小征讲,Eden 區(qū)很快被填滿据某,就會(huì)導(dǎo)致頻繁 Minor GC,因此可以通過(guò)增大新生代空間來(lái)降低 Minor GC 的頻率诗箍。
單次 Minor GC 時(shí)間是由兩部分組成:T1(掃描新生代)和 T2(復(fù)制存活對(duì)象)癣籽。假設(shè)一個(gè)對(duì)象在 Eden 區(qū)的存活時(shí)間為 500ms,Minor GC 的時(shí)間間隔是 300ms滤祖,那么正常情況下筷狼,Minor GC 的時(shí)間為 :T1+T2。
當(dāng)我們?cè)龃笮律臻g匠童,Minor GC 的時(shí)間間隔可能會(huì)擴(kuò)大到 600ms埂材,此時(shí)一個(gè)存活500ms 的對(duì)象就會(huì)在 Eden 區(qū)中被回收掉,此時(shí)就不存在復(fù)制存活對(duì)象了俏让,所以再發(fā)生Minor GC 的時(shí)間為:兩次掃描新生代楞遏,即 2T1茬暇。可見(jiàn)寡喝,擴(kuò)容后糙俗,Minor GC 時(shí)增加了 T1,但省去了 T2 的時(shí)間预鬓。通常在虛擬機(jī)中巧骚,復(fù)制對(duì)象的成本要遠(yuǎn)高于掃描成本。
如果在堆內(nèi)存中存在較多的長(zhǎng)期存活的對(duì)象格二,此時(shí)增加年輕代空間劈彪,反而會(huì)增加 Minor GC的時(shí)間。如果堆中的短期對(duì)象很多顶猜,那么擴(kuò)容新生代沧奴,單次 Minor GC 時(shí)間不會(huì)顯著增加。因此长窄,單次 Minor GC 時(shí)間更多取決于 GC 后存活對(duì)象的數(shù)量滔吠,而非 Eden 區(qū)的大小。
2挠日、降低 Full GC 的頻率?
通常情況下疮绷,由于堆內(nèi)存空間不足或老年代對(duì)象太多,會(huì)觸發(fā) Full GC嚣潜,頻繁的 Full GC 會(huì)帶來(lái)上下文切換冬骚,增加系統(tǒng)的性能開(kāi)銷(xiāo)。常見(jiàn)的方法:
減少創(chuàng)建大對(duì)象?
例如懂算,可能有一個(gè)一次性查詢出 60 個(gè)字段的業(yè)務(wù)操作只冻,這種大對(duì)象如果超過(guò)年輕代最大對(duì)象閾值,會(huì)被直接創(chuàng)建在老年代计技;即使被創(chuàng)建在了年輕代属愤,由于年輕代的內(nèi)存空間有限,通過(guò) Minor GC 之后也會(huì)進(jìn)入到老年代酸役。這種大對(duì)象很容易產(chǎn)生較多
的 Full GC。
此時(shí)驾胆,可以將這種大對(duì)象拆解出來(lái)涣澡,首次只查詢一些比較重要的字段,如果還需要其它字段輔助查看丧诺,再通過(guò)第二次查詢顯示剩余的字段入桂。
增大堆內(nèi)存空間?
在堆內(nèi)存不足的情況下,增大堆內(nèi)存空間驳阎,且設(shè)置初始化堆內(nèi)存為最大堆內(nèi)存抗愁,也可以降低 Full GC 的頻率馁蒂。
3、選擇合適的 GC 回收器
??假設(shè)有這樣一個(gè)需求蜘腌,要求每次操作的響應(yīng)時(shí)間必須在 500ms 以內(nèi)沫屡。這個(gè)時(shí)候我們一般會(huì)選擇響應(yīng)速度較快的 GC 回收器,CMS(Concurrent Mark Sweep)回收器和 G1 回收器都是不錯(cuò)的選擇撮珠。
??而當(dāng)需求對(duì)系統(tǒng)吞吐量有要求時(shí)沮脖,就可以選擇 Parallel Scavenge 回收器來(lái)提高系統(tǒng)的吞吐量。
總結(jié)?:垃圾收集器的種類很多芯急,我們可以將其分成兩種類型勺届,一種是響應(yīng)速度快,一種是吞吐量高娶耍。通常情況下免姿,CMS 和 G1 回收器的響應(yīng)速度快,Parallel Scavenge 回收器的吞吐量高榕酒。
??在 JDK1.8 環(huán)境下胚膊,默認(rèn)使用的是 Parallel Scavenge(年輕代)+Serial Old(老年代)垃圾收集器,你可以通過(guò)文中介紹的查詢 JVM 的 GC 默認(rèn)配置方法進(jìn)行查看奈应。
??通常情況澜掩,JVM 是默認(rèn)垃圾回收優(yōu)化的,在沒(méi)有性能衡量標(biāo)準(zhǔn)的前提下杖挣,盡量避免修改GC 的一些性能配置參數(shù)肩榕。如果一定要改,那就必須基于大量的測(cè)試結(jié)果或線上的具體性能來(lái)進(jìn)行調(diào)整惩妇。
1.3 優(yōu)化JVM內(nèi)存分配
??JVM 內(nèi)存分配不合理最直接的表現(xiàn)就是頻繁的 GC株汉,這會(huì)導(dǎo)致上下文切換等性能問(wèn)題,從而降低系統(tǒng)的吞吐量歌殃、增加系統(tǒng)的響應(yīng)時(shí)間乔妈。因此,如果你在線上環(huán)境或性能測(cè)試時(shí)氓皱,發(fā)現(xiàn)頻繁的 GC路召,且是正常的對(duì)象創(chuàng)建和回收,這個(gè)時(shí)候就需要考慮調(diào)整 JVM 內(nèi)存分配了波材,從而減少 GC 所帶來(lái)的性能開(kāi)銷(xiāo)股淡。
??當(dāng)新建一個(gè)對(duì)象時(shí),對(duì)象會(huì)被優(yōu)先分配到新生代的 Eden 區(qū)中廷区,這時(shí)虛擬機(jī)會(huì)給對(duì)象定義一個(gè)對(duì)象年齡計(jì)數(shù)器(通過(guò)參數(shù) -XX:MaxTenuringThreshold 設(shè)置)唯灵。
??同時(shí),也有另外一種情況隙轻,當(dāng) Eden 空間不足時(shí)埠帕,虛擬機(jī)將會(huì)執(zhí)行一個(gè)新生代的垃圾回收(Minor GC)垢揩。這時(shí) JVM 會(huì)把存活的對(duì)象轉(zhuǎn)移到 Survivor 中,并給對(duì)象的年齡 +1敛瓷。對(duì)象在 Survivor 中同樣也會(huì)經(jīng)歷 MinorGC叁巨,每經(jīng)過(guò)一次 MinorGC,對(duì)象的年齡將會(huì) +1琐驴。
??當(dāng)然了俘种,內(nèi)存空間也是有設(shè)置閾值的,可以通過(guò)參數(shù) -XX:PetenureSizeThreshold 設(shè)置直接被分配到老年代的最大對(duì)象绝淡,這時(shí)如果分配的對(duì)象超過(guò)了設(shè)置的閥值宙刘,對(duì)象就會(huì)直接被分配到老年代,這樣做的好處就是可以減少新生代的垃圾回收牢酵。
??在默認(rèn)不配置 JVM 堆內(nèi)存大小的情況下悬包,JVM 根據(jù)默認(rèn)值來(lái)配置當(dāng)前內(nèi)存大小,示例:
??可以得知在這臺(tái)機(jī)器上啟動(dòng)的 JVM 默認(rèn)最大堆內(nèi)存為 1953MB馍乙,初始化大小為 124MB布近。
??在 JDK1.7 中,默認(rèn)情況下年輕代和老年代的比例是 1:2丝格,我們可以通過(guò)–XX:NewRatio 重置該配置項(xiàng)撑瞧。年輕代中的 Eden 和 To Survivor、From Survivor 的比例是 8:1:1显蝌,可以通過(guò) -XX:SurvivorRatio 重置該配置項(xiàng)预伺。
??在 JDK1.7 中如果開(kāi)啟了 -XX:+UseAdaptiveSizePolicy 配置項(xiàng),JVM 將會(huì)動(dòng)態(tài)調(diào)整 Java堆中各個(gè)區(qū)域的大小以及進(jìn)入老年代的年齡曼尊,–XX:NewRatio 和 -XX:SurvivorRatio 將會(huì)失效酬诀,而 JDK1.8 是默認(rèn)開(kāi)啟 -XX:+UseAdaptiveSizePolicy 配置項(xiàng)的。
??可以提供一些具體的調(diào)優(yōu)方向的指標(biāo):
1骆撇、GC 頻率?
高頻的 FullGC 會(huì)給系統(tǒng)帶來(lái)非常大的性能消耗瞒御,雖然 MinorGC 相對(duì) FullGC 來(lái)說(shuō)好了許多,但過(guò)多的 MinorGC 仍會(huì)給系統(tǒng)帶來(lái)壓力神郊。
2肴裙、內(nèi)存?
這里的內(nèi)存指的是堆內(nèi)存大小,堆內(nèi)存又分為年輕代內(nèi)存和老年代內(nèi)存涌乳。首先我們要分析堆內(nèi)存大小是否合適践宴,其實(shí)是分析年輕代和老年代的比例是否合適。如果內(nèi)存不足或分配不均勻爷怀,會(huì)增加 FullGC,嚴(yán)重的將導(dǎo)致 CPU 持續(xù)爆滿带欢,影響系統(tǒng)性能运授。
3烤惊、吞吐量?
頻繁的 FullGC 將會(huì)引起線程的上下文切換,增加系統(tǒng)的性能開(kāi)銷(xiāo)吁朦,從而影響每次處理的線程請(qǐng)求柒室,最終導(dǎo)致系統(tǒng)的吞吐量下降。
4逗宜、延時(shí)?
JVM 的 GC 持續(xù)時(shí)間也會(huì)影響到每次請(qǐng)求的響應(yīng)時(shí)間雄右。
??具體調(diào)優(yōu)方法:
1、調(diào)整堆內(nèi)存空間減少 FullGC
??如果堆內(nèi)存基本被用完了纺讲,而且存在大量FullGC擂仍,這意味著堆內(nèi)存嚴(yán)重不足,這個(gè)時(shí)候需要調(diào)大堆內(nèi)存空間:
java-jar-Xms4g-Xmx4gheapTest-0.0.1-SNAPSHOT.jar
2熬甚、調(diào)整年輕代減少 MinorGC
??可以將年輕代設(shè)置得大一些逢渔,從而減少一些MinorGC:
java-jar-Xms4g-Xmx4g-Xmn3gheapTest-0.0.1-SNAPSHOT.jar
3、設(shè)置 Eden乡括、Survivor 區(qū)比例
??在 JVM 中肃廓,如果開(kāi)啟 AdaptiveSizePolicy,則每次 GC后都會(huì)重新計(jì)算 Eden诲泌、From Survivor 和 To Survivor 區(qū)的大小盲赊,計(jì)算依據(jù)是 GC 過(guò)程中統(tǒng)計(jì)的 GC 時(shí)間、吞吐量敷扫、內(nèi)存占用量哀蘑。這個(gè)時(shí)候 SurvivorRatio 默認(rèn)設(shè)置的比例會(huì)失效。
??在 JDK1.8 中呻澜,默認(rèn)是開(kāi)啟 AdaptiveSizePolicy 的递礼,我們可以通過(guò) -XX:-UseAdaptiveSizePolicy 關(guān)閉該項(xiàng)配置,或顯示運(yùn)行 -XX:SurvivorRatio=8 將 Eden羹幸、Survivor 的比例設(shè)置為 8:2脊髓。大部分新對(duì)象都是在 Eden 區(qū)創(chuàng)建的,我們可以固定 Eden 區(qū)的占用比例栅受,來(lái)調(diào)優(yōu) JVM 的內(nèi)存分配性能将硝。
總結(jié)?:JVM 內(nèi)存調(diào)優(yōu)通常和 GC 調(diào)優(yōu)是互補(bǔ)的。
1.4 如何內(nèi)存持續(xù)上升問(wèn)題
??平時(shí)排查內(nèi)存性能瓶頸時(shí)屏镊,往往需要用到一些 Linux 命令行或者 JDK 工具來(lái)輔助我們監(jiān)測(cè)系統(tǒng)或者虛擬機(jī)內(nèi)存的使用情況依疼。
1、top?
top 命令可以實(shí)時(shí)顯示正在執(zhí)行進(jìn)程的 CPU 使用率而芥、內(nèi)存使用率以及系統(tǒng)負(fù)載等信息律罢。其中上半部分顯示的是系統(tǒng)的統(tǒng)計(jì)信息,下半部分顯示的是進(jìn)程的使用率統(tǒng)計(jì)信息。示例:
除了簡(jiǎn)單的 top 之外误辑,還可以通過(guò) top -Hp pid 查看具體線程使用系統(tǒng)資源情況:
2沧踏、vmstat?
vmstat 是一款指定采樣周期和次數(shù)的功能性監(jiān)測(cè)工具,它不僅可以統(tǒng)計(jì)內(nèi)存的使用情況巾钉,還可以觀測(cè)到 CPU 的使用率翘狱、swap 的使用情況。但 vmstat 一般很少用來(lái)查看內(nèi)存的使用情況砰苍,而是經(jīng)常被用來(lái)觀察進(jìn)程的上下文切換潦匈。示例:
r:等待運(yùn)行的進(jìn)程數(shù);
b:處于非中斷睡眠狀態(tài)的進(jìn)程數(shù)赚导;
swpd:虛擬內(nèi)存使用情況茬缩;
free:空閑的內(nèi)存;
buff:用來(lái)作為緩沖的內(nèi)存數(shù)辟癌;
si:從磁盤(pán)交換到內(nèi)存的交換頁(yè)數(shù)量寒屯;
so:從內(nèi)存交換到磁盤(pán)的交換頁(yè)數(shù)量;
bi:發(fā)送到塊設(shè)備的塊數(shù)黍少;
bo:從塊設(shè)備接收到的塊數(shù)寡夹;
in:每秒中斷數(shù);
cs:每秒上下文切換次數(shù)厂置;
us:用戶 CPU 使用時(shí)間菩掏;
sy:內(nèi)核 CPU 系統(tǒng)使用時(shí)間;
id:空閑時(shí)間昵济;
wa:等待 I/O 時(shí)間智绸;
st:運(yùn)行虛擬機(jī)竊取的時(shí)間。
3访忿、pidstat
3瞧栗、pidstat
??之前的 top 和 vmstat 兩個(gè)命令都是監(jiān)測(cè)進(jìn)程的內(nèi)存、CPU 以及 I/O 使用情況海铆,而 pidstat 命令則是深入到線程級(jí)別迹恐。
??通過(guò) pidstat -help 命令,可以查看到有以下幾個(gè)常用的參數(shù)來(lái)監(jiān)測(cè)線程的性能:
??常用參數(shù):
-u:默認(rèn)的參數(shù)卧斟,顯示各個(gè)進(jìn)程的 cpu 使用情況殴边;
-r:顯示各個(gè)進(jìn)程的內(nèi)存使用情況;
-d:顯示各個(gè)進(jìn)程的 I/O 使用情況珍语;
-w:顯示每個(gè)進(jìn)程的上下文切換情況锤岸;
-p:指定進(jìn)程號(hào);
-t:顯示進(jìn)程中線程的統(tǒng)計(jì)信息板乙。
??可以通過(guò)相關(guān)命令(例如 ps 或 jps)查詢到相關(guān)進(jìn)程 ID是偷,再運(yùn)行以下命令來(lái)監(jiān)測(cè)該進(jìn)程的內(nèi)存使用情況:
??pidstat 的參數(shù) -p 用于指定進(jìn)程 ID,-r 表示監(jiān)控內(nèi)存的使用情況,1 表示每秒的意思晓猛,3 則表示采樣次數(shù)饿幅。
??上圖中幾個(gè)結(jié)果的意義:
Minflt/s:任務(wù)每秒發(fā)生的次要錯(cuò)誤,不需要從磁盤(pán)中加載頁(yè)戒职;
Majflt/s:任務(wù)每秒發(fā)生的主要錯(cuò)誤,需要從磁盤(pán)中加載頁(yè)透乾;
VSZ:虛擬地址大小洪燥,虛擬內(nèi)存使用 KB;
RSS:常駐集合大小乳乌,非交換區(qū)內(nèi)存使用 KB捧韵。
4、jstat
4汉操、jstat
??jstat 可以監(jiān)測(cè) Java 應(yīng)用程序的實(shí)時(shí)運(yùn)行情況再来,包括堆內(nèi)存信息以及垃圾回收信息。
??一個(gè)常用功能磷瘤,如何使用 jstat 查看堆內(nèi)存的使用情況芒篷。可以用 jstat -gc pid 查看:
S0C:年輕代中 To Survivor 的容量(單位 KB)采缚;
S1C:年輕代中 From Survivor 的容量(單位 KB)针炉;
S0U:年輕代中 To Survivor 目前已使用空間(單位 KB);
S1U:年輕代中 From Survivor 目前已使用空間(單位 KB)扳抽;
EC:年輕代中 Eden 的容量(單位 KB)篡帕;
EU:年輕代中 Eden 目前已使用空間(單位 KB);
OC:Old 代的容量(單位 KB)贸呢;
OU:Old 代目前已使用空間(單位 KB)镰烧;
MC:Metaspace 的容量(單位 KB);
MU:Metaspace 目前已使用空間(單位 KB)楞陷;
YGC:從應(yīng)用程序啟動(dòng)到采樣時(shí)年輕代中 gc 次數(shù)怔鳖;
YGCT:從應(yīng)用程序啟動(dòng)到采樣時(shí)年輕代中 gc 所用時(shí)間 (s);
FGC:從應(yīng)用程序啟動(dòng)到采樣時(shí) old 代(全 gc)gc 次數(shù)猜谚;
FGCT:從應(yīng)用程序啟動(dòng)到采樣時(shí) old 代(全 gc)gc 所用時(shí)間 (s)败砂;
GCT:從應(yīng)用程序啟動(dòng)到采樣時(shí) gc 用的總時(shí)間 (s)。
5魏铅、jstack?
最常用的功能就是使用 jstack pid 命令查看線程的堆棧信息昌犹,通常會(huì)結(jié)合 top -Hp pid 或 pidstat -p pid -t 一起查看具體線程的狀態(tài),也經(jīng)常用來(lái)排查一些死鎖的異常览芳。
每個(gè)線程堆棧的信息中斜姥,都可以查看到線程 ID、線程的狀態(tài)(wait、sleep铸敏、running 等狀態(tài))以及是否持有鎖等缚忧。
6、jmap?
可以使用 jmap 輸出堆內(nèi)存中的對(duì)象信息杈笔,包括產(chǎn)生了哪些對(duì)象闪水,對(duì)象數(shù)量多少等。
可以用 jmap 來(lái)查看堆內(nèi)存初始化配置信息以及堆內(nèi)存的使用情況:
可以使用 jmap -histo[:live] pid 查看堆內(nèi)存中的對(duì)象數(shù)目蒙具、大小統(tǒng)計(jì)直方圖球榆,如果帶上 live 則只統(tǒng)計(jì)活對(duì)象:
可以通過(guò) jmap 命令把堆內(nèi)存的使用情況 dump 到文件中:
??平時(shí)遇到的內(nèi)存溢出問(wèn)題一般分為兩種,一種是由于大峰值下沒(méi)有限流禁筏,瞬間創(chuàng)建大量對(duì)象而導(dǎo)致的內(nèi)存溢出持钉;另一種則是由于內(nèi)存泄漏而導(dǎo)致的內(nèi)存溢出。使用限流篱昔,一般就可以解決第一種內(nèi)存溢出問(wèn)題每强,但其實(shí)很多時(shí)候,內(nèi)存溢出往往是內(nèi)存泄漏導(dǎo)致的州刽,這種問(wèn)題就是程序的 BUG空执,需要及時(shí)找到問(wèn)題代碼。
二怀伦、設(shè)計(jì)模式優(yōu)化
2.1 原型模式與享元模式優(yōu)化
??原型模式和享元模式脆烟,前者是在創(chuàng)建多個(gè)實(shí)例時(shí),對(duì)創(chuàng)建過(guò)程的性能進(jìn)行調(diào)優(yōu)房待;后者是用減少創(chuàng)建實(shí)例的方式邢羔,來(lái)調(diào)優(yōu)系統(tǒng)性能。
??它們的使用是分場(chǎng)景的桑孩。在有些場(chǎng)景下拜鹤,我們需要重復(fù)創(chuàng)建多個(gè)實(shí)例,例如在循環(huán)體中賦值一個(gè)對(duì)象流椒,此時(shí)我們就可以采用原型模式來(lái)優(yōu)化對(duì)象的創(chuàng)建過(guò)程敏簿;而在有些場(chǎng)景下,我們則可以避免重復(fù)創(chuàng)建多個(gè)實(shí)例宣虾,在內(nèi)存中共享對(duì)象就好了
??要實(shí)現(xiàn)一個(gè)原型類惯裕,需要具備三個(gè)條件:
實(shí)現(xiàn) Cloneable 接口重寫(xiě) Object 類中的clone方法在重寫(xiě)的clone方法中調(diào)用 super.clone()
??其實(shí)深拷貝就是基于淺拷貝來(lái)遞歸實(shí)現(xiàn)具體的每個(gè)對(duì)象。
??在一些重復(fù)創(chuàng)建對(duì)象的場(chǎng)景下绣硝,我們就可以使用原型模式來(lái)提高對(duì)象的創(chuàng)建性能蜻势。如循環(huán)體內(nèi)創(chuàng)建對(duì)象時(shí),我們就可以考慮用 clone 的方式來(lái)實(shí)現(xiàn)鹉胖,示例:
for(inti=0;i<list.size();i++){
Studentstu=newStudent();...}//可以優(yōu)化為:Studentstu=newStudent();for(inti=0;i<list.size();i++){
Studentstu1=(Student)stu.clone();...}
??享元模式是運(yùn)用共享技術(shù)有效地最大限度地復(fù)用細(xì)粒度對(duì)象的一種模式握玛。該模式中够傍,以對(duì)象的信息狀態(tài)劃分,可以分為內(nèi)部數(shù)據(jù)和外部數(shù)據(jù)挠铲。內(nèi)部數(shù)據(jù)是對(duì)象可以共享出來(lái)的信息冕屯,這些信息不會(huì)隨著系統(tǒng)的運(yùn)行而改變;外部數(shù)據(jù)則是在不同運(yùn)行時(shí)被標(biāo)記了不同的值拂苹。
??享元模式在實(shí)際開(kāi)發(fā)中的應(yīng)用也非常廣泛安聘。例如 Java 的 String 字符串,在一些字符串常量中瓢棒,會(huì)共享常量池中字符串對(duì)象搞挣,從而減少重復(fù)創(chuàng)建相同值對(duì)象,占用內(nèi)存空間音羞。
2.2 裝飾器模式優(yōu)化電商系統(tǒng)中復(fù)雜的商品價(jià)格策略
??裝飾器模式主要用來(lái)優(yōu)化業(yè)務(wù)的復(fù)雜度,它不僅簡(jiǎn)化了業(yè)務(wù)代碼仓犬,還優(yōu)化了業(yè)務(wù)代碼的結(jié)構(gòu)設(shè)計(jì)嗅绰,使得整個(gè)業(yè)務(wù)邏輯清晰、易讀易懂搀继。通常窘面,裝飾器模式用于擴(kuò)展一個(gè)類的功能,且支持動(dòng)態(tài)添加和刪除類的功能叽躯。在裝飾器模式中财边,裝飾類和被裝飾類都只關(guān)心自身的業(yè)務(wù),不相互干擾点骑,真正實(shí)現(xiàn)了解耦酣难。