分享JVM調(diào)優(yōu)的幾種策略——只要思想不滑坡执虹,辦法總比困難多

JVM調(diào)優(yōu)的幾種策略

一疯溺、JVM內(nèi)存模型及垃圾收集算法

1.根據(jù)Java虛擬機(jī)規(guī)范JVM將內(nèi)存劃分為:

New(年輕代)

Tenured(年老代)

永久代(Perm)

其中New和Tenured屬于堆內(nèi)存朴下,堆內(nèi)存會(huì)從JVM啟動(dòng)參數(shù)(-Xmx:3G)指定的內(nèi)存中分配,Perm不屬于堆內(nèi)存苦蒿,有虛擬機(jī)直接分配殴胧,但可以通過(guò)-XX:PermSize -XX:MaxPermSize 等參數(shù)調(diào)整其大小。

年輕代(New):年輕代用來(lái)存放JVM剛分配的Java對(duì)象

年老代(Tenured):年輕代中經(jīng)過(guò)垃圾回收沒(méi)有回收掉的對(duì)象將被Copy到年老代

永久代(Perm):永久代存放Class佩迟、Method元信息团滥,其大小跟項(xiàng)目的規(guī)模、類(lèi)报强、方法的量有關(guān)灸姊,一般設(shè)置為128M就足夠,設(shè)置原則是預(yù)留30%的空間秉溉。

New又分為幾個(gè)部分:

Eden:Eden用來(lái)存放JVM剛分配的對(duì)象

Survivor1

Survivro2:兩個(gè)Survivor空間一樣大厨钻,當(dāng)Eden中的對(duì)象經(jīng)過(guò)垃圾回收沒(méi)有被回收掉時(shí),會(huì)在兩個(gè)Survivor之間來(lái)回Copy坚嗜,當(dāng)滿(mǎn)足某個(gè)條件夯膀,比如Copy次數(shù),就會(huì)被Copy到Tenured苍蔬。顯然诱建,Survivor只是增加了對(duì)象在年輕代中的逗留時(shí)間,增加了被垃圾回收的可能性碟绑。

2.垃圾回收算法

垃圾回收算法可以分為三類(lèi)俺猿,都基于標(biāo)記-清除(復(fù)制)算法:

Serial算法(單線程)

并行算法

并發(fā)算法

JVM會(huì)根據(jù)機(jī)器的硬件配置對(duì)每個(gè)內(nèi)存代選擇適合的回收算法,比如格仲,如果機(jī)器多于1個(gè)核押袍,會(huì)對(duì)年輕代選擇并行算法,關(guān)于選擇細(xì)節(jié)請(qǐng)參考JVM調(diào)優(yōu)文檔凯肋。

稍微解釋下的是谊惭,并行算法是用多線程進(jìn)行垃圾回收,回收期間會(huì)暫停程序的執(zhí)行侮东,而并發(fā)算法圈盔,也是多線程回收,但期間不停止應(yīng)用執(zhí)行悄雅。所以驱敲,并發(fā)算法適用于交互性高的一些程序。經(jīng)過(guò)觀察宽闲,并發(fā)算法會(huì)減少年輕代的大小众眨,其實(shí)就是使用了一個(gè)大的年老代握牧,這反過(guò)來(lái)跟并行算法相比吞吐量相對(duì)較低。

還有一個(gè)問(wèn)題是娩梨,垃圾回收動(dòng)作何時(shí)執(zhí)行我碟?

當(dāng)年輕代內(nèi)存滿(mǎn)時(shí),會(huì)引發(fā)一次普通GC姚建,該GC僅回收年輕代矫俺。需要強(qiáng)調(diào)的時(shí),年輕代滿(mǎn)是指Eden代滿(mǎn)掸冤,Survivor滿(mǎn)不會(huì)引發(fā)GC

當(dāng)年老代滿(mǎn)時(shí)會(huì)引發(fā)Full GC厘托,F(xiàn)ull GC將會(huì)同時(shí)回收年輕代、年老代

當(dāng)永久代滿(mǎn)時(shí)也會(huì)引發(fā)Full GC稿湿,會(huì)導(dǎo)致Class铅匹、Method元信息的卸載

另一個(gè)問(wèn)題是,何時(shí)會(huì)拋出OutOfMemoryException饺藤,并不是內(nèi)存被耗空的時(shí)候才拋出

JVM98%的時(shí)間都花費(fèi)在內(nèi)存回收

每次回收的內(nèi)存小于2%

滿(mǎn)足這兩個(gè)條件將觸發(fā)OutOfMemoryException包斑,這將會(huì)留給系統(tǒng)一個(gè)微小的間隙以做一些Down之前的操作,比如手動(dòng)打印Heap Dump涕俗。

二罗丰、內(nèi)存泄漏及解決方法

1.系統(tǒng)崩潰前的一些現(xiàn)象:

每次垃圾回收的時(shí)間越來(lái)越長(zhǎng),由之前的10ms延長(zhǎng)到50ms左右再姑,F(xiàn)ullGC的時(shí)間也有之前的0.5s延長(zhǎng)到4哨免、5s

FullGC的次數(shù)越來(lái)越多族沃,最頻繁時(shí)隔不到1分鐘就進(jìn)行一次FullGC

年老代的內(nèi)存越來(lái)越大并且每次FullGC后年老代沒(méi)有內(nèi)存被釋放

之后系統(tǒng)會(huì)無(wú)法響應(yīng)新的請(qǐng)求畦粮,逐漸到達(dá)OutOfMemoryError的臨界值掌测。

2.生成堆的dump文件

通過(guò)JMX的MBean生成當(dāng)前的Heap信息,大小為一個(gè)3G(整個(gè)堆的大衅芤伞)的hprof文件讨永,如果沒(méi)有啟動(dòng)JMX可以通過(guò)Java的jmap命令來(lái)生成該文件。

3.分析dump文件

下面要考慮的是如何打開(kāi)這個(gè)3G的堆信息文件遇革,顯然一般的Window系統(tǒng)沒(méi)有這么大的內(nèi)存卿闹,必須借助高配置的Linux。當(dāng)然我們可以借助X-Window把Linux上的圖形導(dǎo)入到Window澳淑。我們考慮用下面幾種工具打開(kāi)該文件:

Visual VM

IBM HeapAnalyzer

JDK 自帶的Hprof工具

使用這些工具時(shí)為了確保加載速度比原,建議設(shè)置最大內(nèi)存為6G。使用后發(fā)現(xiàn)杠巡,這些工具都無(wú)法直觀地觀察到內(nèi)存泄漏,Visual VM雖能觀察到對(duì)象大小雇寇,但看不到調(diào)用堆棧氢拥;HeapAnalyzer雖然能看到調(diào)用堆棧蚌铜,卻無(wú)法正確打開(kāi)一個(gè)3G的文件。因此嫩海,我們又選用了Eclipse專(zhuān)門(mén)的靜態(tài)內(nèi)存分析工具:Mat冬殃。

4.分析內(nèi)存泄漏

通過(guò)Mat我們能清楚地看到,哪些對(duì)象被懷疑為內(nèi)存泄漏叁怪,哪些對(duì)象占的空間最大及對(duì)象的調(diào)用關(guān)系审葬。針對(duì)本案,在ThreadLocal中有很多的JbpmContext實(shí)例奕谭,經(jīng)過(guò)調(diào)查是JBPM的Context沒(méi)有關(guān)閉所致涣觉。

另,通過(guò)Mat或JMX我們還可以分析線程狀態(tài)血柳,可以觀察到線程被阻塞在哪個(gè)對(duì)象上官册,從而判斷系統(tǒng)的瓶頸。

5.回歸問(wèn)題

Q:為什么崩潰前垃圾回收的時(shí)間越來(lái)越長(zhǎng)难捌?

A:根據(jù)內(nèi)存模型和垃圾回收算法膝宁,垃圾回收分兩部分:內(nèi)存標(biāo)記、清除(復(fù)制)根吁,標(biāo)記部分只要內(nèi)存大小固定時(shí)間是不變的员淫,變的是復(fù)制部分,因?yàn)槊看卫厥斩加幸恍┗厥詹坏舻膬?nèi)存击敌,所以增加了復(fù)制量满粗,導(dǎo)致時(shí)間延長(zhǎng)。所以愚争,垃圾回收的時(shí)間也可以作為判斷內(nèi)存泄漏的依據(jù)

Q:為什么Full GC的次數(shù)越來(lái)越多映皆?

A:因此內(nèi)存的積累,逐漸耗盡了年老代的內(nèi)存轰枝,導(dǎo)致新對(duì)象分配沒(méi)有更多的空間捅彻,從而導(dǎo)致頻繁的垃圾回收

Q:為什么年老代占用的內(nèi)存越來(lái)越大?

A:因?yàn)槟贻p代的內(nèi)存無(wú)法被回收鞍陨,越來(lái)越多地被Copy到年老代

三步淹、性能調(diào)優(yōu)

除了上述內(nèi)存泄漏外,我們還發(fā)現(xiàn)CPU長(zhǎng)期不足3%诚撵,系統(tǒng)吞吐量不夠缭裆,針對(duì)8core×16G、64bit的Linux服務(wù)器來(lái)說(shuō)寿烟,是嚴(yán)重的資源浪費(fèi)澈驼。

在CPU負(fù)載不足的同時(shí),偶爾會(huì)有用戶(hù)反映請(qǐng)求的時(shí)間過(guò)長(zhǎng)筛武,我們意識(shí)到必須對(duì)程序及JVM進(jìn)行調(diào)優(yōu)缝其。從以下幾個(gè)方面進(jìn)行:

線程池:解決用戶(hù)響應(yīng)時(shí)間長(zhǎng)的問(wèn)題

連接池

JVM啟動(dòng)參數(shù):調(diào)整各代的內(nèi)存比例和垃圾回收算法挎塌,提高吞吐量

程序算法:改進(jìn)程序邏輯算法提高性能

1.Java線程池(java.util.concurrent.ThreadPoolExecutor)

大多數(shù)JVM6上的應(yīng)用采用的線程池都是JDK自帶的線程池,之所以把成熟的Java線程池進(jìn)行羅嗦說(shuō)明内边,是因?yàn)樵摼€程池的行為與我們想象的有點(diǎn)出入榴都。Java線程池有幾個(gè)重要的配置參數(shù):

corePoolSize:核心線程數(shù)(最新線程數(shù))

maximumPoolSize:最大線程數(shù),超過(guò)這個(gè)數(shù)量的任務(wù)會(huì)被拒絕漠其,用戶(hù)可以通過(guò)RejectedExecutionHandler接口自定義處理方式

keepAliveTime:線程保持活動(dòng)的時(shí)間

workQueue:工作隊(duì)列嘴高,存放執(zhí)行的任務(wù)

Java線程池需要傳入一個(gè)Queue參數(shù)(workQueue)用來(lái)存放執(zhí)行的任務(wù),而對(duì)Queue的不同選擇和屎,線程池有完全不同的行為:

SynchronousQueue:?一個(gè)無(wú)容量的等待隊(duì)列拴驮,一個(gè)線程的insert操作必須等待另一線程的remove操作,采用這個(gè)Queue線程池將會(huì)為每個(gè)任務(wù)分配一個(gè)新線程

LinkedBlockingQueue :?無(wú)界隊(duì)列眶俩,采用該Queue莹汤,線程池將忽略maximumPoolSize參數(shù),僅用corePoolSize的線程處理所有的任務(wù)颠印,未處理的任務(wù)便在LinkedBlockingQueue中排隊(duì)

ArrayBlockingQueue: 有界隊(duì)列纲岭,在有界隊(duì)列和?maximumPoolSize的作用下,程序?qū)⒑茈y被調(diào)優(yōu):更大的Queue和小的maximumPoolSize將導(dǎo)致CPU的低負(fù)載线罕;小的Queue和大的池止潮,Queue就沒(méi)起動(dòng)應(yīng)有的作用。

其實(shí)我們的要求很簡(jiǎn)單钞楼,希望線程池能跟連接池一樣喇闸,能設(shè)置最小線程數(shù)、最大線程數(shù)询件,當(dāng)最小數(shù)<任務(wù)<最大數(shù)時(shí)燃乍,應(yīng)該分配新的線程處理;當(dāng)任務(wù)>最大數(shù)時(shí)宛琅,應(yīng)該等待有空閑線程再處理該任務(wù)刻蟹。

但線程池的設(shè)計(jì)思路是,任務(wù)應(yīng)該放到Queue中嘿辟,當(dāng)Queue放不下時(shí)再考慮用新線程處理舆瘪,如果Queue滿(mǎn)且無(wú)法派生新線程,就拒絕該任務(wù)红伦。設(shè)計(jì)導(dǎo)致“先放等執(zhí)行”英古、“放不下再執(zhí)行”、“拒絕不等待”昙读。所以召调,根據(jù)不同的Queue參數(shù),要提高吞吐量不能一味地增大maximumPoolSize。

當(dāng)然某残,要達(dá)到我們的目標(biāo)国撵,必須對(duì)線程池進(jìn)行一定的封裝陵吸,幸運(yùn)的是ThreadPoolExecutor中留了足夠的自定義接口以幫助我們達(dá)到目標(biāo)玻墅。我們封裝的方式是:

以SynchronousQueue作為參數(shù),使maximumPoolSize發(fā)揮作用壮虫,以防止線程被無(wú)限制的分配澳厢,同時(shí)可以通過(guò)提高maximumPoolSize來(lái)提高系統(tǒng)吞吐量

自定義一個(gè)RejectedExecutionHandler,當(dāng)線程數(shù)超過(guò)maximumPoolSize時(shí)進(jìn)行處理囚似,處理方式為隔一段時(shí)間檢查線程池是否可以執(zhí)行新Task剩拢,如果可以把拒絕的Task重新放入到線程池,檢查的時(shí)間依賴(lài)keepAliveTime的大小饶唤。

2.連接池(org.apache.commons.dbcp.BasicDataSource)

在使用org.apache.commons.dbcp.BasicDataSource的時(shí)候徐伐,因?yàn)橹安捎昧四J(rèn)配置,所以當(dāng)訪問(wèn)量大時(shí)募狂,通過(guò)JMX觀察到很多Tomcat線程都阻塞在BasicDataSource使用的Apache ObjectPool的鎖上办素,直接原因當(dāng)時(shí)是因?yàn)锽asicDataSource連接池的最大連接數(shù)設(shè)置的太小,默認(rèn)的BasicDataSource配置祸穷,僅使用8個(gè)最大連接性穿。

我還觀察到一個(gè)問(wèn)題,當(dāng)較長(zhǎng)的時(shí)間不訪問(wèn)系統(tǒng)雷滚,比如2天需曾,DB上的Mysql會(huì)斷掉所以的連接,導(dǎo)致連接池中緩存的連接不能用祈远。為了解決這些問(wèn)題呆万,我們充分研究了BasicDataSource,發(fā)現(xiàn)了一些優(yōu)化的點(diǎn):

Mysql默認(rèn)支持100個(gè)鏈接车份,所以每個(gè)連接池的配置要根據(jù)集群中的機(jī)器數(shù)進(jìn)行谋减,如有2臺(tái)服務(wù)器,可每個(gè)設(shè)置為60

initialSize:參數(shù)是一直打開(kāi)的連接數(shù)

minEvictableIdleTimeMillis:該參數(shù)設(shè)置每個(gè)連接的空閑時(shí)間躬充,超過(guò)這個(gè)時(shí)間連接將被關(guān)閉

timeBetweenEvictionRunsMillis:后臺(tái)線程的運(yùn)行周期逃顶,用來(lái)檢測(cè)過(guò)期連接

maxActive:最大能分配的連接數(shù)

maxIdle:最大空閑數(shù),當(dāng)連接使用完畢后發(fā)現(xiàn)連接數(shù)大于maxIdle充甚,連接將被直接關(guān)閉以政。只有initialSize < x < maxIdle的連接將被定期檢測(cè)是否超期。這個(gè)參數(shù)主要用來(lái)在峰值訪問(wèn)時(shí)提高吞吐量伴找。

initialSize是如何保持的盈蛮?經(jīng)過(guò)研究代碼發(fā)現(xiàn),BasicDataSource會(huì)關(guān)閉所有超期的連接技矮,然后再打開(kāi)initialSize數(shù)量的連接抖誉,這個(gè)特性與minEvictableIdleTimeMillis殊轴、timeBetweenEvictionRunsMillis一起保證了所有超期的initialSize連接都會(huì)被重新連接,從而避免了Mysql長(zhǎng)時(shí)間無(wú)動(dòng)作會(huì)斷掉連接的問(wèn)題袒炉。

3.JVM參數(shù)

在JVM啟動(dòng)參數(shù)中旁理,可以設(shè)置跟內(nèi)存、垃圾回收相關(guān)的一些參數(shù)設(shè)置我磁,默認(rèn)情況不做任何設(shè)置JVM會(huì)工作的很好孽文,但對(duì)一些配置很好的Server和具體的應(yīng)用必須仔細(xì)調(diào)優(yōu)才能獲得最佳性能。通過(guò)設(shè)置我們希望達(dá)到一些目標(biāo):

GC的時(shí)間足夠的小

GC的次數(shù)足夠的少

發(fā)生Full GC的周期足夠的長(zhǎng)

前兩個(gè)目前是相悖的夺艰,要想GC時(shí)間小必須要一個(gè)更小的堆芋哭,要保證GC次數(shù)足夠少,必須保證一個(gè)更大的堆郁副,我們只能取其平衡减牺。

(1)針對(duì)JVM堆的設(shè)置,一般可以通過(guò)-Xms -Xmx限定其最小存谎、最大值拔疚,為了防止垃圾收集器在最小、最大之間收縮堆而產(chǎn)生額外的時(shí)間愕贡,我們通常把最大草雕、最小設(shè)置為相同的值

(2)年輕代和年老代將根據(jù)默認(rèn)的比例(1:2)分配堆內(nèi)存,可以通過(guò)調(diào)整二者之間的比率NewRadio來(lái)調(diào)整二者之間的大小固以,也可以針對(duì)回收代墩虹,比如年輕代,通過(guò) -XX:newSize -XX:MaxNewSize來(lái)設(shè)置其絕對(duì)大小憨琳。同樣诫钓,為了防止年輕代的堆收縮,我們通常會(huì)把-XX:newSize -XX:MaxNewSize設(shè)置為同樣大小

(3)年輕代和年老代設(shè)置多大才算合理篙螟?這個(gè)我問(wèn)題毫無(wú)疑問(wèn)是沒(méi)有答案的菌湃,否則也就不會(huì)有調(diào)優(yōu)。我們觀察一下二者大小變化有哪些影響

更大的年輕代必然導(dǎo)致更小的年老代遍略,大的年輕代會(huì)延長(zhǎng)普通GC的周期惧所,但會(huì)增加每次GC的時(shí)間;小的年老代會(huì)導(dǎo)致更頻繁的Full GC

更小的年輕代必然導(dǎo)致更大年老代绪杏,小的年輕代會(huì)導(dǎo)致普通GC很頻繁下愈,但每次的GC時(shí)間會(huì)更短;大的年老代會(huì)減少Full GC的頻率

如何選擇應(yīng)該依賴(lài)應(yīng)用程序對(duì)象生命周期的分布情況:如果應(yīng)用存在大量的臨時(shí)對(duì)象蕾久,應(yīng)該選擇更大的年輕代势似;如果存在相對(duì)較多的持久對(duì)象,年老代應(yīng)該適當(dāng)增大。但很多應(yīng)用都沒(méi)有這樣明顯的特性履因,在抉擇時(shí)應(yīng)該根據(jù)以下兩點(diǎn):(A)本著Full GC盡量少的原則障簿,讓年老代盡量緩存常用對(duì)象,JVM的默認(rèn)比例1:2也是這個(gè)道理 (B)通過(guò)觀察應(yīng)用一段時(shí)間栅迄,看其他在峰值時(shí)年老代會(huì)占多少內(nèi)存站故,在不影響Full GC的前提下,根據(jù)實(shí)際情況加大年輕代霞篡,比如可以把比例控制在1:1世蔗。但應(yīng)該給年老代至少預(yù)留1/3的增長(zhǎng)空間

(4)在配置較好的機(jī)器上(比如多核端逼、大內(nèi)存)朗兵,可以為年老代選擇并行收集算法:?-XX:+UseParallelOldGC?,默認(rèn)為Serial收集

(5)線程堆棧的設(shè)置:每個(gè)線程默認(rèn)會(huì)開(kāi)啟1M的堆棧顶滩,用于存放棧幀余掖、調(diào)用參數(shù)、局部變量等礁鲁,對(duì)大多數(shù)應(yīng)用而言這個(gè)默認(rèn)值太了盐欺,一般256K就足用。理論上仅醇,在內(nèi)存不變的情況下冗美,減少每個(gè)線程的堆棧,可以產(chǎn)生更多的線程析二,但這實(shí)際上還受限于操作系統(tǒng)粉洼。

(4)可以通過(guò)下面的參數(shù)打Heap Dump信息

-XX:HeapDumpPath

-XX:+PrintGCDetails

-XX:+PrintGCTimeStamps

-Xloggc:/usr/aaa/dump/heap_trace.txt

通過(guò)下面參數(shù)可以控制OutOfMemoryError時(shí)打印堆的信息

-XX:+HeapDumpOnOutOfMemoryError

請(qǐng)看一下一個(gè)時(shí)間的Java參數(shù)配置:(服務(wù)器:Linux 64Bit,8Core×16G)

JAVA_OPTS="$JAVA_OPTS -server -Xms3G -Xmx3G -Xss256k -XX:PermSize=128m -XX:MaxPermSize=128m -XX:+UseParallelOldGC -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/aaa/dump -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:/usr/aaa/dump/heap_trace.txt -XX:NewSize=1G -XX:MaxNewSize=1G"

經(jīng)過(guò)觀察該配置非常穩(wěn)定叶摄,每次普通GC的時(shí)間在10ms左右属韧,F(xiàn)ull GC基本不發(fā)生,或隔很長(zhǎng)很長(zhǎng)的時(shí)間才發(fā)生一次

通過(guò)分析dump文件可以發(fā)現(xiàn)蛤吓,每個(gè)1小時(shí)都會(huì)發(fā)生一次Full GC宵喂,經(jīng)過(guò)多方求證,只要在JVM中開(kāi)啟了JMX服務(wù)会傲,JMX將會(huì)1小時(shí)執(zhí)行一次Full GC以清除引用

調(diào)優(yōu)方法

一切都是為了這一步锅棕,調(diào)優(yōu),在調(diào)優(yōu)之前淌山,我們需要記住下面的原則:

1裸燎、多數(shù)的Java應(yīng)用不需要在服務(wù)器上進(jìn)行GC優(yōu)化;

2艾岂、多數(shù)導(dǎo)致GC問(wèn)題的Java應(yīng)用顺少,都不是因?yàn)槲覀儏?shù)設(shè)置錯(cuò)誤,而是代碼問(wèn)題;

3脆炎、在應(yīng)用上線之前梅猿,先考慮將機(jī)器的JVM參數(shù)設(shè)置到最優(yōu)(最適合);

4秒裕、減少創(chuàng)建對(duì)象的數(shù)量袱蚓;

5、減少使用全局變量和大對(duì)象几蜻;

6喇潘、GC優(yōu)化是到最后不得已才采用的手段;

7梭稚、在實(shí)際使用中颖低,分析GC情況優(yōu)化代碼比優(yōu)化GC參數(shù)要多得多;

GC優(yōu)化的目的有兩個(gè)

1弧烤、將轉(zhuǎn)移到老年代的對(duì)象數(shù)量降低到最谐佬肌;

2暇昂、減少full GC的執(zhí)行時(shí)間莺戒;

為了達(dá)到上面的目的,一般地急波,你需要做的事情有:

1从铲、減少使用全局變量和大對(duì)象;

2澄暮、調(diào)整新生代的大小到最合適名段;

3、設(shè)置老年代的大小為最合適赏寇;

4吉嫩、選擇合適的GC收集器;

在上面的4條方法中嗅定,用了幾個(gè)“合適”自娩,那究竟什么才算合適,一般的渠退,請(qǐng)參考上面“收集器搭配”和“啟動(dòng)內(nèi)存分配”兩節(jié)中的建議忙迁。但這些建議不是萬(wàn)能的,需要根據(jù)您的機(jī)器和應(yīng)用情況進(jìn)行發(fā)展和變化碎乃,實(shí)際操作中姊扔,可以將兩臺(tái)機(jī)器分別設(shè)置成不同的GC參數(shù),并且進(jìn)行對(duì)比梅誓,選用那些確實(shí)提高了性能或減少了GC時(shí)間的參數(shù)恰梢。

真正熟練的使用GC調(diào)優(yōu)佛南,是建立在多次進(jìn)行GC監(jiān)控和調(diào)優(yōu)的實(shí)戰(zhàn)經(jīng)驗(yàn)上的,進(jìn)行監(jiān)控和調(diào)優(yōu)的一般步驟為:

1嵌言,監(jiān)控GC的狀態(tài)

使用各種JVM工具嗅回,查看當(dāng)前日志,分析當(dāng)前JVM參數(shù)設(shè)置摧茴,并且分析當(dāng)前堆內(nèi)存快照和gc日志绵载,根據(jù)實(shí)際的各區(qū)域內(nèi)存劃分和GC執(zhí)行時(shí)間,覺(jué)得是否進(jìn)行優(yōu)化苛白;

2娃豹,分析結(jié)果,判斷是否需要優(yōu)化

如果各項(xiàng)參數(shù)設(shè)置合理购裙,系統(tǒng)沒(méi)有超時(shí)日志出現(xiàn)懂版,GC頻率不高,GC耗時(shí)不高缓窜,那么沒(méi)有必要進(jìn)行GC優(yōu)化定续;如果GC時(shí)間超過(guò)1-3秒,或者頻繁GC禾锤,則必須優(yōu)化;

注:如果滿(mǎn)足下面的指標(biāo)摹察,則一般不需要進(jìn)行GC:

Minor GC執(zhí)行時(shí)間不到50ms恩掷;

Minor GC執(zhí)行不頻繁,約10秒一次供嚎;

Full GC執(zhí)行時(shí)間不到1s黄娘;

Full GC執(zhí)行頻率不算頻繁,不低于10分鐘1次克滴;

3逼争,調(diào)整GC類(lèi)型和內(nèi)存分配

如果內(nèi)存分配過(guò)大或過(guò)小,或者采用的GC收集器比較慢劝赔,則應(yīng)該優(yōu)先調(diào)整這些參數(shù)誓焦,并且先找1臺(tái)或幾臺(tái)機(jī)器進(jìn)行beta,然后比較優(yōu)化過(guò)的機(jī)器和沒(méi)有優(yōu)化的機(jī)器的性能對(duì)比着帽,并有針對(duì)性的做出最后選擇杂伟;

4,不斷的分析和調(diào)整

通過(guò)不斷的試驗(yàn)和試錯(cuò)仍翰,分析并找到最合適的參數(shù)

5赫粥,全面應(yīng)用參數(shù)

如果找到了最合適的參數(shù),則將這些參數(shù)應(yīng)用到所有服務(wù)器予借,并進(jìn)行后續(xù)跟蹤越平。

調(diào)優(yōu)實(shí)例

上面的內(nèi)容都是紙上談兵频蛔,下面我們以一些真實(shí)例子來(lái)進(jìn)行說(shuō)明:

實(shí)例1:

筆者昨日發(fā)現(xiàn)部分開(kāi)發(fā)測(cè)試機(jī)器出現(xiàn)異常:java.lang.OutOfMemoryError: GC overhead limit exceeded,這個(gè)異常代表:

GC為了釋放很小的空間卻耗費(fèi)了太多的時(shí)間秦叛,其原因一般有兩個(gè):1帽驯,堆太小,2书闸,有死循環(huán)或大對(duì)象尼变;

筆者首先排除了第2個(gè)原因,因?yàn)檫@個(gè)應(yīng)用同時(shí)是在線上運(yùn)行的浆劲,如果有問(wèn)題嫌术,早就掛了。所以懷疑是這臺(tái)機(jī)器中堆設(shè)置太信平琛度气;

使用ps -ef |grep "java"查看,發(fā)現(xiàn):

該應(yīng)用的堆區(qū)設(shè)置只有768m膨报,而機(jī)器內(nèi)存有2g磷籍,機(jī)器上只跑這一個(gè)java應(yīng)用,沒(méi)有其他需要占用內(nèi)存的地方现柠。另外院领,這個(gè)應(yīng)用比較大,需要占用的內(nèi)存也比較多够吩;

筆者通過(guò)上面的情況判斷比然,只需要改變堆中各區(qū)域的大小設(shè)置即可,于是改成下面的情況:

跟蹤運(yùn)行情況發(fā)現(xiàn)周循,相關(guān)異常沒(méi)有再出現(xiàn)强法;

實(shí)例2:

一個(gè)服務(wù)系統(tǒng),經(jīng)常出現(xiàn)卡頓湾笛,分析原因饮怯,發(fā)現(xiàn)Full GC時(shí)間太長(zhǎng)

jstat -gcutil:

S0 S1 E O P YGC YGCT FGC FGCT GCT

12.16 0.00 5.18 63.78 20.32 54 2.047 5 6.946 8.993

分析上面的數(shù)據(jù),發(fā)現(xiàn)Young GC執(zhí)行了54次嚎研,耗時(shí)2.047秒蓖墅,每次Young GC耗時(shí)37ms,在正常范圍嘉赎,而Full GC執(zhí)行了5次置媳,耗時(shí)6.946秒,每次平均1.389s公条,數(shù)據(jù)顯示出來(lái)的問(wèn)題是:Full GC耗時(shí)較長(zhǎng)拇囊,分析該系統(tǒng)的是指發(fā)現(xiàn),NewRatio=9靶橱,也就是說(shuō)寥袭,新生代和老生代大小之比為1:9路捧,這就是問(wèn)題的原因:

1,新生代太小传黄,導(dǎo)致對(duì)象提前進(jìn)入老年代杰扫,觸發(fā)老年代發(fā)生Full GC;

2膘掰,老年代較大章姓,進(jìn)行Full GC時(shí)耗時(shí)較大;

優(yōu)化的方法是調(diào)整NewRatio的值识埋,調(diào)整到4凡伊,發(fā)現(xiàn)Full GC沒(méi)有再發(fā)生,只有Young GC在執(zhí)行窒舟。這就是把對(duì)象控制在新生代就清理掉系忙,沒(méi)有進(jìn)入老年代(這種做法對(duì)一些應(yīng)用是很有用的,但并不是對(duì)所有應(yīng)用都要這么做)

實(shí)例3:

一應(yīng)用在性能測(cè)試過(guò)程中惠豺,發(fā)現(xiàn)內(nèi)存占用率很高银还,F(xiàn)ull GC頻繁,使用sudo -u admin -H jmap -dump:format=b,file=文件名.hprof pid 來(lái)dump內(nèi)存洁墙,生成dump文件蛹疯,并使用Eclipse下的mat差距進(jìn)行分析,發(fā)現(xiàn):

從圖中可以看出扫俺,這個(gè)線程存在問(wèn)題苍苞,隊(duì)列LinkedBlockingQueue所引用的大量對(duì)象并未釋放,導(dǎo)致整個(gè)線程占用內(nèi)存高達(dá)378m狼纬,此時(shí)通知開(kāi)發(fā)人員進(jìn)行代碼優(yōu)化,將相關(guān)對(duì)象釋放掉即可骂际。

這里給大家提供一個(gè)學(xué)習(xí)交流的平臺(tái)疗琉,java架構(gòu)師群:671017482

具有1-5工作經(jīng)驗(yàn)的,面對(duì)目前流行的技術(shù)不知從何下手歉铝,需要突破技術(shù)瓶頸的可以加群盈简。

在公司待久了,過(guò)得很安逸太示,但跳槽時(shí)面試碰壁柠贤。需要在短時(shí)間內(nèi)進(jìn)修、跳槽拿高薪的可以加群类缤。

如果沒(méi)有工作經(jīng)驗(yàn)臼勉,但基礎(chǔ)非常扎實(shí),對(duì)java工作機(jī)制餐弱,常用設(shè)計(jì)思想宴霸,常用java開(kāi)發(fā)框架掌握熟練的可以加群囱晴。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市瓢谢,隨后出現(xiàn)的幾起案子畸写,更是在濱河造成了極大的恐慌,老刑警劉巖氓扛,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件枯芬,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡采郎,警方通過(guò)查閱死者的電腦和手機(jī)千所,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)尉剩,“玉大人真慢,你說(shuō)我怎么就攤上這事±砭ィ” “怎么了黑界?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)皂林。 經(jīng)常有香客問(wèn)我朗鸠,道長(zhǎng),這世上最難降的妖魔是什么础倍? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任烛占,我火速辦了婚禮,結(jié)果婚禮上沟启,老公的妹妹穿的比我還像新娘忆家。我一直安慰自己,他們只是感情好德迹,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布芽卿。 她就那樣靜靜地躺著,像睡著了一般胳搞。 火紅的嫁衣襯著肌膚如雪卸例。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,301評(píng)論 1 301
  • 那天肌毅,我揣著相機(jī)與錄音筷转,去河邊找鬼。 笑死悬而,一個(gè)胖子當(dāng)著我的面吹牛呜舒,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播摊滔,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼阴绢,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼店乐!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起呻袭,我...
    開(kāi)封第一講書(shū)人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤眨八,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后左电,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體廉侧,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年篓足,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了段誊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡栈拖,死狀恐怖连舍,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情涩哟,我是刑警寧澤索赏,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站贴彼,受9級(jí)特大地震影響潜腻,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜器仗,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一融涣、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧精钮,春花似錦威鹿、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至弹沽,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間筋粗,已是汗流浹背策橘。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留娜亿,地道東北人丽已。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像买决,于是被迫代替她去往敵國(guó)和親沛婴。 傳聞我的和親對(duì)象是個(gè)殘疾皇子吼畏,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

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

  • 參數(shù)設(shè)置 在Java虛擬機(jī)的參數(shù)中,有3種表示方法用“ps -ef |grep "java"命令嘁灯,可以得到當(dāng)前Ja...
    九問(wèn)閱讀 9,143評(píng)論 2 52
  • 1.一些概念 1.1.數(shù)據(jù)類(lèi)型 Java虛擬機(jī)中泻蚊,數(shù)據(jù)類(lèi)型可以分為兩類(lèi):基本類(lèi)型和引用類(lèi)型〕笮觯基本類(lèi)型的變量保存原始...
    落落落落大大方方閱讀 4,540評(píng)論 4 86
  • 最近工作中性雄,老是遇到程序假死或者宕掉,最終原因都是full gc導(dǎo)致羹奉,剛好回過(guò)頭再學(xué)習(xí)一下JVM內(nèi)存模式秒旋,以及GC...
    VIPSHOP_FCS閱讀 1,720評(píng)論 0 4
  • Java 虛擬機(jī)有自己完善的硬件架構(gòu), 如處理器、堆棧诀拭、寄存器等迁筛,還具有相應(yīng)的指令系統(tǒng)。JVM 屏蔽了與具體操作系...
    尹小凱閱讀 1,689評(píng)論 0 10
  • 原文閱讀 前言 這段時(shí)間懈怠了耕挨,罪過(guò)细卧! 最近看到有同事也開(kāi)始用上了微信公眾號(hào)寫(xiě)博客了,挺好的~給他們點(diǎn)贊俗孝,這博客我...
    碼農(nóng)戲碼閱讀 5,962評(píng)論 2 31