GC时间过长优化方法
??應用運行過程中是不希望出現長時間的GC停頓的,因為這會影響服務的可用性,導致用戶體驗變差,甚至會嚴重損害一些關鍵的應用程序。本文將會列出可能導致GC停頓時間長的一些原因和解決方案。
對象創(chuàng)建的速度過高
??如果應用創(chuàng)建對象的速度非常高,隨之而來的就是GC頻率也會變快,然后會導致GC的停頓時間變長。所以說,優(yōu)化代碼以降低對象的創(chuàng)建速率是降低GC停頓時間最有效的方法。可以使用JProfiler, YourKit, JVisualVM這樣的性能監(jiān)控工具來幫助優(yōu)化對象的創(chuàng)建速度,這些工具會分析出:應用到底創(chuàng)建了哪些對象?對象創(chuàng)建的速度是多少?這些對象占用了多少內存空間?是誰創(chuàng)建的這些對象?
Young區(qū)過小
??如果Young過小,對象就會過早的晉升到Old區(qū),Old區(qū)的垃圾回收一般比Young區(qū)會花費更多的時間,因此,可以通過增大Young區(qū)來有效的降低長時間GC停頓。可以用下面兩個JVM參數來設置Young區(qū)的大小:
-Xmn: 設置Young區(qū)所占的字節(jié)數 -XX:NewRatio: 設置Old區(qū)和Young區(qū)的比例, 比如說,-XX:NewRatio=3也就是說Old區(qū)和Young區(qū)的比例是3:1, Young區(qū)占整個堆的1/4,如果堆是2G,那么Young區(qū)就是0.5G。選擇合適的GC算法
??GC算法是影響GC停頓時間的一個非常重要的因素。選擇使用G1收集器,因為G1是自動調優(yōu)的,只需要設置一個停頓時間的目標就可以了,比如: -XX:MaxGCPauseMillis=200。這個例子設置了最大停頓時間的目標是200ms,JVM會盡最大努力來滿足這個目標。
進程被交換(Swap)出內存
??有時候由于系統(tǒng)內存不足,操作系統(tǒng)會把應用從內存中交換出去。Swap是非常耗時的,因為需要訪問磁盤,相對于訪問物理內存來說要慢得多的多。生產環(huán)境下的應用是不應該被Swap出內存的。當發(fā)生進程Swap的時候,GC停頓時間也會變長。如果應用被Swap了,你需要:
a:給機器增加內存
b:減少機器上運行的進程數,以釋放更多的內存
c:減少應用分配的內存(不推薦,可能會引起其他問題)
GC線程數過少
GC日志中的每一個GC事件都會打印user、sys、real time,比如:
[Times: user=25.56 sys=0.35, real=20.48 secs]如果GC日志中,real time并不是明顯比user time小,這就說明GC線程數是不夠的,這就需要增加GC線程了。假如說,user time是25秒,GC線程數是5,那么real time大概是5左右才是正常的(25/5=5)。注意:GC線程過多會占用大量的系統(tǒng)CPU,從而會影響應用能使用的CPU資源。
IO負載重
??如果系統(tǒng)的IO負載很重(大量的文件讀寫)也會導致GC停頓時間過長。這些IO讀寫不一定是應用引起的,可能是機器上其他的進程導致的,但是這仍然會導致應用的停頓時間變長。當IO負載很重的時候,real time會明顯比user time長,比如:
[Times: user=0.20 sys=0.01, real=18.45 secs]如果發(fā)生了這種情況,可以這么辦:
a:如果是應用導致的,優(yōu)化代碼
b:如果是別的進程導致的,把它殺掉或者遷走
c:把應用遷到一個IO負載小的機器上
tip:如何來監(jiān)控IO負載?在linux上可以用sar命令來監(jiān)控IO的負載:sar -d -p 1,這個命令每隔一秒會打印一次每秒的讀寫數量。
顯式調用了System.gc()
??當調用了System.gc()或者是Runtime.getRuntime().gc()以后,就會導致FullGC。FullGC的過程當中,整個JVM是暫停的(所有的應用都被暫停掉)。System.gc()可能是以下幾種情況產生的:
a:應用的程序員手動調用了System.gc()
b:應用引用的三方庫或者框架甚至是應用服務器可能調用了System.gc()
c:可能是由外部使用了JMX的工具觸發(fā)
d:如果應用使用了RMI,RMI會每隔一段時間調用一次System.gc(),這個時間間隔是可以設置的:
– Dsun.rmi.dgc.server.gcInterval=n
– Dsun.rmi.dgc.client.gcInterval=n
什么情況下,會手動調用System.gc()
??一般來說,在編寫Java代碼并將其留給JVM時,不要考慮內存管理。在一些特殊情況下,如正在編寫一個性能基準,可以在運行之間調用System.gc()。??MappedByteBuffer用于需要最佳IO性能的地方。MappedByteBuffer提供到底層文件的直接內存映射。內存映射文件的許多細節(jié)固有地依賴于底層操作系統(tǒng),因此是未指定的。當請求的區(qū)域未完全包含在該通道的文件中時,此方法的行為是未指定的。對該程序或另一個的底層文件的內容或大小進行的更改是否被傳播到緩沖區(qū)是未指定的。沒有指定將緩沖區(qū)的更改傳播到文件的速率。
??因為MappedByteBuffer依賴于操作系統(tǒng),垃圾收集器不能立即回收。但是當調用System.gc()垃圾收集器釋放句柄,可以刪除該文件。在使用MappedByteBuffer的同時,如果我們正在使用內存敏感程序,那么最好調用System.gc()。
堆內存過大
??堆內存過大也會導致GC停頓時間過長,如果堆內存過大,那么堆中就會累計過多的垃圾,當發(fā)生FullGC要回收所有的垃圾的時候,就會花費更多的時間。如果你的JVM的堆內存有18G,可以考慮分成3個6G的JVM實例,堆內存小會降低GC的停頓時間。
GC任務分配不均
??就算有多個GC線程,線程之間的任務分配可能也不是均衡的,這個可能有很多種原因:
a:掃描大的線性的數據結構目前是無法并行的。
b:有些GC事件只發(fā)生在單個線程上,比如CMS中的‘concurrent mode failure’。如果你碰巧使用的CMS,可以使用-XX:+CMSScavengeBeforeRemark 這個參數,它可以讓多個GC線程之間任務分配的更平均。
總結
以上是生活随笔為你收集整理的GC时间过长优化方法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 基于百度LBS的定位
- 下一篇: ROUTEOS使用笔记之二