OOM簡記
notice: 下面說的比如10M老年代空間埃疫,在10M分配完畢的時(shí)候進(jìn)行FullGC都是簡化的說法栓霜,其實(shí)應(yīng)該是有個(gè)空間分配擔(dān)保機(jī)制的存在横蜒,不會(huì)出現(xiàn)在10M全部使用的情況下才進(jìn)行FullGC的情況。
1. OOME出現(xiàn)的區(qū)域
heap java堆
vm stack 虛擬機(jī)棧
native method stack 本地方法棧
method area 方法區(qū)
direct memory 直接內(nèi)存
ps. 除了program counter register 程序計(jì)數(shù)器外的其他內(nèi)存區(qū)域都有可能發(fā)生oome
2. 怎么判斷OOME出現(xiàn)的區(qū)域
查看異常堆棧信息丛晌,一般在OOME后會(huì)進(jìn)一步跟著提示具體的區(qū)域,比如
Exception in thread "Thread-0" java.lang.OutOfMemoryError: Java heap space
at com.cqx.oom.OOMDemo.lambda$main$0(OOMDemo.java:23)
at com.cqx.oom.OOMDemo$Lambda$1/668386784.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
3. 什么時(shí)候會(huì)拋出OOME
當(dāng)創(chuàng)建對象時(shí)jvm檢測到內(nèi)存不夠本次內(nèi)存分配澎蛛,于是會(huì)進(jìn)行一次FullGC,如果本次GC后還是內(nèi)存不夠分配呆馁,那就拋出OOME浙滤,當(dāng)前的線程就會(huì)死亡气堕。
OOME Thrown when the Java Virtual Machine cannot allocate an object because it is out of memory, and no more memory could be made available by the garbage collector. ----API文檔
The JVM will run the GC when it's on edge of the OutOfMemoryError. If the GC didn't help at all, then the JVM will throw OOME.
具體情況可能發(fā)生OOME的情況如下
假設(shè)jvm的參數(shù)設(shè)置現(xiàn)在是 -Xmx20M -Xmx20M -Xmn10M -XX:MaxDirectMemorySize=1M, 即分配堆大小為20M,其中老年代和新生代各10M
public static final int _1MB = 1024 * 1024;
1. 持續(xù)不斷創(chuàng)建對象,并不釋放他的引用
for(i = 0; i < 100; i++) {
byte[] M1 = new byte[_1MB];
}
隨著對象不斷創(chuàng)建揖膜,tenured generation到達(dá)了10MB,即已經(jīng)裝滿了次氨,然后繼續(xù)創(chuàng)建對象摘投,隨著新生代中Eden區(qū)再次被填滿,觸發(fā)一次Minor GC幸撕,之前Survivor0中部分對象由于各種原因外臂,比如年齡大了,或者本次MinorGC中發(fā)現(xiàn)S0不夠存放本次存活的對象貌矿,所以會(huì)有對象晉升到Old Generation。此時(shí)JVM發(fā)現(xiàn)老年代的內(nèi)存也不夠進(jìn)行此次分配黑低,于是就進(jìn)行一次FullGC(Major GC)克握,根據(jù)可達(dá)性分析枷踏,從GC ROOTS開始分析對象的引用關(guān)系,發(fā)現(xiàn)任然存活停团,就不進(jìn)行回收下梢,所有本次FullGC并沒有任何卵用,所有GC后還是分配不了,那么此時(shí)當(dāng)前線程就會(huì)拋出一個(gè)OOME番电,然后死亡。
2. byte[] _12MB = new byte[12 * _1MB];
大對象的創(chuàng)建會(huì)根據(jù)JVM參數(shù)的設(shè)置直接分配到老年代中这刷,跟1類似娩井,F(xiàn)ULLGC后發(fā)現(xiàn)內(nèi)存不夠洞辣,拋出OOME
3. ByteBuffer.allocateDirect(2 * _1MB);
由于之前指定了最大直接內(nèi)存為1MB 這邊分配了2MB就拋出了OOME
java.lang.OutOfMemoryError: Direct buffer memory
4. 方法區(qū)OOME
之前在第一次接 Jenkins發(fā)布平臺(tái)時(shí)就出現(xiàn)了這個(gè)問題。當(dāng)時(shí)發(fā)布www定鸟?指定的jdk版本是7著瓶,可能是因?yàn)閣ww大量的jsp文件(感覺也不多),在編譯生成class文件時(shí)導(dǎo)致方法區(qū)不夠用發(fā)生了OOME沸久。看當(dāng)時(shí)打印的異常日志確認(rèn)是方法區(qū)的OOME子刮。
原因: jdk8之前版本不通過-XX:PermSize和-XX:MaxPermSize顯示指定方法區(qū)大小诵竭,應(yīng)該就是64MB卵慰。當(dāng)時(shí)的jenkins中配置只指定了堆大小,沒有指定方法區(qū)的大小
解決: 1. -XX:PermSize和-XX:MaxPermSize 2.改用jdk8(移除了使用永久代來當(dāng)做方法區(qū)的策略病线,使用了metaspace元數(shù)據(jù)區(qū),不暫用jvm指定的堆內(nèi)存鲤嫡,而是使用機(jī)器的native memory,不受jvm堆的限制)
5. 調(diào)用外部服務(wù)惕耕,外部服務(wù)故障或者處理緩慢導(dǎo)致OOME
比如調(diào)用中交興路查詢位置接口诫肠,網(wǎng)絡(luò)或者ZJXL服務(wù)原因請求一直得不到反饋,兩邊處理速度不對等挤安,導(dǎo)致越來越多的請求積壓丧鸯,OOME。
思路:
a. 使用hystrix等熔斷功能的工具來管理外部服務(wù)調(diào)用
b. 復(fù)用tcp連接围肥∨跋龋可以考慮加上keepalive:true的請求頭派敷,避免每次調(diào)用都重新發(fā)起tcp連接撰洗,
c. httpclient這種應(yīng)該是要單例的差导,不需要每次請求都單獨(dú)創(chuàng)建
d. 生產(chǎn)者消費(fèi)者模式來處理
6. 內(nèi)存泄漏
a. 監(jiān)聽器和一些回調(diào)注冊上來但是沒有顯示的取消猪勇,服務(wù)端一直持有這個(gè)監(jiān)聽器的引用。
b. 緩存泄漏助析,比如用HashMap實(shí)現(xiàn)的緩存椅您,吧引用扔到進(jìn)去后忘了,一直扔一直扔就炸了雪隧,GC的時(shí)候發(fā)現(xiàn)對象引用仍然在緩存里员舵,就不回收就炸了∽矗可以用WeakHashMap來實(shí)現(xiàn)韭邓,key是WeakReference,GC可以回收掉。
</pre>
4. OOME會(huì)導(dǎo)致JVM shutdown嗎
不會(huì)可很。OOME只會(huì)把拋出這個(gè)異常的線程給殺掉。 之所以發(fā)生OOME的時(shí)候應(yīng)用程序經(jīng)常出現(xiàn)假死苇本,是因?yàn)镺OM了菜拓,其他正常運(yùn)行的線程也無法分配到資源,各種請求都無法得到處理俺夕,給人一種應(yīng)用掛掉的感覺裳凸。
如果你想在OOME的時(shí)候主動(dòng)kill掉當(dāng)前的應(yīng)用可以
-XX:OnOutOfMemoryError="kill -9 %p" 這里還可以執(zhí)行你的腳本姨谷,這樣就可以自動(dòng)重啟梦湘。件甥。。
%p is the current Java process PID placeholder.
5. 上面說的OOME會(huì)殺死當(dāng)前的線程引有,那么GC可以回收到這部分資源,釋放空間弄捕,那么為什么應(yīng)用程序還是無法響應(yīng)請求呢守谓。
因?yàn)橥鶔伋鯫OME的那個(gè)線程您单,不是大頭,他只是壓垮駱駝的最后一根稻草虐秦。
比如一個(gè)應(yīng)用,堆最大設(shè)為10M蜈彼,一個(gè)定時(shí)任務(wù)的線程讀取數(shù)據(jù)俺驶,在內(nèi)存里生成了上萬個(gè)對象,進(jìn)行處理暮现,此時(shí)用了9.9MB內(nèi)存空間。然后一個(gè)普通的查詢請求進(jìn)來拍顷,OOME了昔案,這個(gè)請求線程死亡了,由于定時(shí)任務(wù)線程還在處理爱沟,GC只把請求線程的資源回收。那么在定時(shí)任務(wù)線程處理期間身冀,還是會(huì)一直出現(xiàn)OOME括享。
6. JVM可以自動(dòng)從OOME恢復(fù)嗎
可以的,但是不建議剩愧。
例如5中的情況娇斩,定時(shí)任務(wù)線程處理完畢了仁卷,那么GC會(huì)吧這部分內(nèi)存釋放掉锦积,程序又正常了歉嗓。 但是發(fā)生OOME的時(shí)候不建議讓JVM自動(dòng)恢復(fù)。因?yàn)槌霈F(xiàn)了這個(gè)異常一定是你程序上有漏洞哮幢,有問題志珍,就算本次恢復(fù)了,接下去很可能還是會(huì)出現(xiàn)這個(gè)問題钢悲。而且OOM恢復(fù)期間很難熬舔株,不管是對于JVM還是用戶來說还棱。具體其他的可以看下面的鏈接
比如之前遇到的,之前做的司機(jī)證件導(dǎo)入功能办铡,就發(fā)生過一次OOME。這個(gè)功能是處理用戶導(dǎo)入的照片文件寡具,服務(wù)器接收這些文件童叠,處理并存放到MongoDB。當(dāng)時(shí)用戶間隔很短時(shí)間導(dǎo)入兩次大約1G的照片文件五垮,第一次導(dǎo)入的請求還在執(zhí)行中杜秸,因?yàn)檎掌枰x取到內(nèi)存,照片文件序列化生成的大對象诞挨,直接進(jìn)入老年代惶傻,MinorGC沒辦法清理,占用了大量的內(nèi)存达罗。緊接著第二次導(dǎo)入請求進(jìn)來了粮揉,在處理過程中由于內(nèi)存不夠OOME了抚笔。但是這并不影響第一次的那個(gè)請求線程。當(dāng)時(shí)jmap -dump:live,format=b,file=path pid
dump了堆棧信息辐宾,發(fā)現(xiàn)內(nèi)存夠用啊怎么會(huì)發(fā)生OOME呢膨蛮,覺得很奇怪敞葛。然后我讓用戶再次導(dǎo)入,發(fā)現(xiàn)還是OOME持偏。后面就突然發(fā)現(xiàn)-dump:live
或者-histo:live
會(huì)先觸發(fā)一次FullGC再進(jìn)行內(nèi)存信息收集,所以出現(xiàn)異常的那個(gè)線程的內(nèi)存都被回收掉了鸿秆,所以dump文件上看來是正常的卿叽。 所以我就讓用戶先別導(dǎo),過個(gè)半個(gè)小時(shí)在來操作附帽,這樣就好了蕉扮。
live指令的解釋
dump only live objects; if not specified, all objects in the heap are dumped.</pre>
7. 怎么應(yīng)對OOME
考慮在JVM啟動(dòng)參數(shù)就設(shè)置上
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath
喳钟,但是有風(fēng)險(xiǎn)屁使,比如5中例子奔则,一直會(huì)觸發(fā)OOME,一直會(huì)生成dump文件酬蹋,會(huì)更加麻煩范抓,所以可以考慮加上-XX:OnOutOfMemoryError="kill -9 %p"
如果1沒做食铐,那么可以考慮用
jmap
來生成dump堆棧快照迅速保留現(xiàn)場信息象泵,并重啟應(yīng)用(1 2G可能幾分鐘就好了偶惠?洲鸠??扒腕?沒找到數(shù)據(jù),懶得試)萤悴。在此之前可以用jstats
查看GC的情況如果JVM的分配內(nèi)存很大瘾腰,好幾G,jmap可能需要執(zhí)行很久才生成dump文件覆履,可以考慮用gdb來處理蹋盆,好像會(huì)快很多。不管他不是jdk自帶的工具硝全,要自己下載栖雾。參考鏈接
考慮使用softReference、weakReference伟众、weakHashMap等等析藕。
不用jmap,使用編程式方式觸發(fā)生成當(dāng)前的堆轉(zhuǎn)儲(chǔ)快照凳厢,參考鏈接
8. 堆轉(zhuǎn)儲(chǔ)快照分析
工具: jhat/visualVM/JPofiler/MAT 語句: OQL(對象查詢語句)
9. spring actuator/JMX Java Management Extensions
actuator 應(yīng)用監(jiān)控的東西,暴露了一些http接口,有個(gè)事heapdump還有個(gè)stackdump先紫,可以通過定時(shí)任務(wù)之類去訪問然后分析居夹,如果有問題就通知之類的准脂。
jmx 公司有平臺(tái)radar意狠。可以多學(xué)習(xí)下這方面的東西院塞。
10. 死鎖與死循環(huán),CPU飆升相關(guān)排查
jstack 看線程堆棧
top查找cpu占用率高的pid top -p pid -H 查看進(jìn)程內(nèi)線程的pid
pid轉(zhuǎn)16進(jìn)制,去jstack中查日志
https://blog.51cto.com/13732225/2347907
. . . . . .
參考的文檔鏈接
https://stackoverflow.com/questions/12096403/java-shutting-down-on-out-of-memory-error
https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html