你是否有過這樣的經(jīng)歷,跑得好好的Java進(jìn)程,突然就癱瘓了。過于依賴Java虛擬機(jī)導(dǎo)致我們對(duì)問題無從下手,問題反復(fù)出現(xiàn)影響開發(fā)效率。其實(shí),多數(shù)Java進(jìn)程癱瘓的原因可以從java虛擬機(jī)層面找到原因,本文列舉出導(dǎo)致Java進(jìn)程癱瘓的一些共性原因,供大家交流和學(xué)習(xí)。
一、內(nèi)存回收一直是java的痛點(diǎn)
用Java無法做出類似Redis這樣的產(chǎn)品。java的內(nèi)存回收機(jī)制使我們?cè)诰帉懘a時(shí)不需要關(guān)注對(duì)象的回收,同時(shí)加大了內(nèi)存回收的消耗,標(biāo)記復(fù)制需要做內(nèi)存拷貝,標(biāo)記清除算法則需要stop the world。所以我們?cè)谑褂镁彺娴臅r(shí)候,量稍微大一些就需要借助類似Redis這樣的中間件幫我們處理了。作為Javaer,我們享受了自動(dòng)內(nèi)存回收的安逸,同時(shí)也需要多了解下內(nèi)存優(yōu)化的方法。
二、為什么fgc停不下來了
1.什么情況下會(huì)gc
為了了解我們的系統(tǒng)為什么會(huì)不停fgc,我們需要先了解一下系統(tǒng)什么情況下會(huì)gc。在jvm層面,當(dāng)我們new一個(gè)對(duì)象的時(shí)候,jvm會(huì)先在堆區(qū)分配對(duì)象需要的內(nèi)存,這個(gè)時(shí)候如果內(nèi)存不夠的話,就需要gc了,gc的返回結(jié)果就是對(duì)象的空間地址。jvm會(huì)先進(jìn)行ygc,也就是我們通常說的標(biāo)記復(fù)制,如果ygc之后依然申請(qǐng)不到空間,就會(huì)進(jìn)行fgc了。同理,如果fgc之后依然沒有足夠的空間,就會(huì)循環(huán)的進(jìn)行fgc,直到申請(qǐng)到足夠的空間。
2.導(dǎo)致不停的fgc的原因
如上文所講,fgc有可能發(fā)生在你的每一行代碼。如果fgc之后依然沒有足夠的空間,就會(huì)不停的fgc,直到申請(qǐng)到足夠的空間。同時(shí)JVM會(huì)限制在拋出OutOfMemory錯(cuò)誤之前在GC中花費(fèi)的VM時(shí)間的比例。系統(tǒng)頻繁FGC大致有五種情況:
內(nèi)存泄漏
請(qǐng)求處理變慢導(dǎo)致同時(shí)申請(qǐng)內(nèi)存的線程太多
metaspace 耗盡
常量池將堆區(qū)占滿
堆外內(nèi)存耗盡
1w,正常情況下處理一個(gè)請(qǐng)求的時(shí)間是1ms,那同一時(shí)刻并行的請(qǐng)求數(shù)量?jī)H為10。如果性能發(fā)生抖動(dòng),每個(gè)請(qǐng)求處理的時(shí)間增加到100ms,那同一時(shí)刻并行的請(qǐng)求數(shù)量就會(huì)增加到100個(gè)。每個(gè)線程在處理請(qǐng)求的時(shí)候都會(huì)new一些對(duì)象出來,長(zhǎng)時(shí)間存活的線程會(huì)造成類似內(nèi)存泄漏的效果,將系統(tǒng)的內(nèi)存耗盡。同時(shí)fgc也會(huì)加劇系統(tǒng)性能的開銷,使系統(tǒng)變得更慢,產(chǎn)生雪崩。
三、如何讓系統(tǒng)fgc之后仍然能活下來
1.杜絕內(nèi)存泄漏
內(nèi)存泄漏造成系統(tǒng)癱瘓的頻率很高,有些系統(tǒng)定時(shí)從數(shù)據(jù)庫拉取配置信息緩存到集合中,但是set不小心寫成了list,最終在新增元素的時(shí)候內(nèi)存溢出了。養(yǎng)成良好的編程習(xí)慣,多關(guān)注些細(xì)節(jié),就能避免很多未知的問題。
2.并發(fā)限制:防止系統(tǒng)被撐死
每臺(tái)服務(wù)器都有并行處理請(qǐng)求的上限,不管請(qǐng)求處理的多快,超過上限之后就會(huì)被撐死,對(duì)高并發(fā)的請(qǐng)求做好并發(fā)數(shù)限制是保持系統(tǒng)穩(wěn)定的必要條件。需要注意的是,有一些系統(tǒng)在拒絕過多的請(qǐng)求時(shí),也會(huì)做一些降級(jí)邏輯,降級(jí)邏輯也是有性能開銷的,同樣需要做并發(fā)限制,如果降級(jí)的請(qǐng)求超過并發(fā)限制,將不進(jìn)行降級(jí)邏輯直接拋出異常。我們可使用的限流組件有很多,推薦我們阿里自研的Sentinel 和 Netflix開源的Hystrix。
3.自適應(yīng)限流:防止系統(tǒng)被摸死
我們需要自適應(yīng)限流有兩個(gè)原因:
a. 每臺(tái)服務(wù)器所處的環(huán)境是不一樣的
有些服務(wù)器和離線計(jì)算的vm混部在一起,有些部署在實(shí)體機(jī),有些部署在新老型號(hào)的機(jī)器上,每臺(tái)服務(wù)器能承受的qps并不完全一樣。統(tǒng)一配置分布式系統(tǒng)中每臺(tái)服務(wù)器限流閥值,要么發(fā)揮不出每臺(tái)服務(wù)器應(yīng)有的作用,要么在高qps的情況下一些比較慢的服務(wù)器宕機(jī),所以用服務(wù)器作為限流粒度是最合適的。
b.設(shè)置了正確的限流閥值,也可能被摸死
當(dāng)單機(jī)承受的QPS 6~20倍于限流的流量時(shí),拒絕一次請(qǐng)求的開銷就無法忽略不記了。譬如春晚活動(dòng)有些系統(tǒng)設(shè)置了正確的限流也被6~20倍于限流的流量沖垮。這種死法稱為被摸死。應(yīng)對(duì)這種情況,我們可以做的是在受到6~20倍的大流量時(shí),動(dòng)態(tài)減少限流的閥值。比如系統(tǒng)最開始接受1000qps,5000的拒絕流量過來會(huì)把系統(tǒng)摸死,這個(gè)時(shí)候我們調(diào)整系統(tǒng)的閥值,限流設(shè)置到100,被摸死的閥值就可以高一些,這樣就算有6000個(gè)請(qǐng)求進(jìn)來,我們系統(tǒng)也可以保證活下來。
4.異常流量監(jiān)控:防止長(zhǎng)尾請(qǐng)求拖垮系統(tǒng)
我們盯系統(tǒng)監(jiān)控的時(shí)候通常會(huì)關(guān)注99分位的數(shù)據(jù),但如果設(shè)置了合理的限流,系統(tǒng)依然被流量打掛,就要從那百分之一的長(zhǎng)尾數(shù)據(jù)入手了。有些長(zhǎng)尾數(shù)據(jù)對(duì)系統(tǒng)的影響會(huì)非常大。想象如果一個(gè)put請(qǐng)求傳過來幾十兆的數(shù)據(jù),對(duì)java是極為不友好的,很有可能產(chǎn)生fgc,讓請(qǐng)求變慢,導(dǎo)致一系列問題。
總之,磨刀不誤砍柴工,當(dāng)我們的系統(tǒng)因?yàn)閒gc一次又一次重啟的時(shí)候,不如花時(shí)間了解下系統(tǒng)產(chǎn)生性能問題的原因,將產(chǎn)生問題的那根針拔掉,晚上睡個(gè)安穩(wěn)覺,白天更加充滿活力的挖新坑。希望每個(gè)程序員手里都是一個(gè)穩(wěn)定的系統(tǒng)。
![]() |