天天看點

跑的好好的 Java 程序,怎麼突然就癱瘓了

記憶體回收一直是 Java的痛點

用 Java 無法做出類似 Redis 這樣的産品。Java 的記憶體回收機制使我們在編寫代碼時不需要關注對象的回收,同時加大了記憶體回收的消耗,标記複制需要做記憶體拷貝,标記清除算法則需要 stop the world 。是以我們在使用緩存的時候,量稍微大一些就需要借助類似 Redis 這樣的中間件幫我們處理了。作為 Javaer ,我們享受了自動記憶體回收的安逸,同時也需要多了解下記憶體優化的方法。

為什麼 FGC 停不下來了

什麼情況下會 GC

為了了解我們的系統為什麼會不停 FGC ,我們需要先了解一下系統什麼情況下會 GC 。在 Jvm 層面,當我們 new 一個對象的時候, Jvm 會先在堆區配置設定對象需要的記憶體,這個時候如果記憶體不夠的話,就需要 GC 了, GC 的傳回結果就是對象的空間位址。Jvm 會先進行 ygc ,也就是我們通常說的标記複制,如果 ygc 之後依然申請不到空間,就會進行 FGC 了。同理,如果 FGC 之後依然沒有足夠的空間,就會循環的進行 FGC ,直到申請到足夠的空間。

導緻不停的 FGC 的原因

如上文所講, FGC 有可能發生在你的每一行代碼。如果 FGC 之後依然沒有足夠的空間,就會不停的 FGC ,直到申請到足夠的空間。同時 JVM 會限制在抛出 OutOfMemory 錯誤之前在 GC 中花費的 VM 時間的比例。系統頻繁 F 大緻有五種情況:

  • 記憶體洩漏
  • 請求處理變慢導緻同時申請記憶體的線程太多
  • metaspace 耗盡
  • 常量池将堆區占滿
  • 堆外記憶體耗盡

在一個高并發的系統中,多數 FGC 是請求處理變慢導緻的。假設單機承受 tps 是1w,正常情況下處理一個請求的時間是 1ms ,那同一時刻并行的請求數量僅為 10 。如果性能發生抖動,每個請求處理的時間增加到 100ms ,那同一時刻并行的請求數量就會增加到 100 個。每個線程在處理請求的時候都會 new 一些對象出來,長時間存活的線程會造成類似記憶體洩漏的效果,将系統的記憶體耗盡。同時 FGC 也會加劇系統性能的開銷,使系統變得更慢,産生雪崩。

如何讓系統 FGC 之後仍然能活下來

杜絕記憶體洩漏

記憶體洩漏産生的原因以及解決辦法網上有很多資料,這裡就不寫了。記憶體洩漏造成系統癱瘓的頻率很高,有些系統定時從資料庫拉取配置資訊緩存到集合中,但是 set 不小心寫成了 list ,最終在新增元素的時候記憶體溢出了。養成良好的程式設計習慣,多關注些細節,就能避免很多未知的問題。

并發限制:防止系統被撐死

每台伺服器都有并行處理請求的上限,不管請求處理的多快,超過上限之後就會被撐死,對高并發的請求做好并發數限制是保持系統穩定的必要條件。需要注意的是,有一些系統在拒絕過多的請求時,也會做一些降級邏輯,降級邏輯也是有性能開銷的,同樣需要做并發限制,如果降級的請求超過并發限制,将不進行降級邏輯直接抛出異常。

自适應限流:防止系統被摸死

我們需要自适應限流有兩個原因:

每台伺服器所處的環境是不一樣的

有些伺服器和離線計算的 vm 混部在一起,有些部署在實體機,有些部署在新老型号的機器上,每台伺服器能承受的 qps 并不完全一樣。統一配置分布式系統中每台伺服器限流閥值,要麼發揮不出每台伺服器應有的作用,要麼在高 qps 的情況下一些比較慢的伺服器當機,是以用伺服器作為限流粒度是最合适的。

設定了正确的限流閥值,也可能被摸死

當單機承受的 QPS 6~20 倍于限流的流量時,拒絕一次請求的開銷就無法忽略不記了。譬如春晚活動有些系統設定了正确的限流也被 6~20 倍于限流的流量沖垮。這種死法稱為被摸死。應對這種情況,我們可以做的是在受到 6~20 倍的大流量時,動态減少限流的閥值。比如系統最開始接受 1000qps ,5000 的拒絕流量過來會把系統摸死,這個時候我們調整系統的閥值,限流設定到 100 ,被摸死的閥值就可以高一些,這樣就算有 6000 個請求進來,我們系統也可以保證活下來。

阿裡有結合算法動态調整單機限流閥的産品,已經對外公布了,感興趣的同學可以搜一下淘系技術公衆号中的 諾亞自适應限流 的相關内容。

異常流量監控:防止長尾請求拖垮系統

我們盯系統監控的時候通常會關注 99 分位的資料,但如果設定了合理的限流,系統依然被流量打挂,就要從那百分之一的長尾資料入手了。有些長尾資料對系統的影響會非常大。想象如果一個 put 請求傳過來幾十兆的資料,對 Java 是極為不友好的,很有可能産生 FGC ,讓請求變慢,導緻一系列問題。

總之,磨刀不誤砍柴工,當我們的系統因為 FGC 一次又一次重新開機的時候,不如花時間了解下系統産生性能問題的原因,将産生問題的那根針拔掉,晚上睡個安穩覺,白天更加充滿活力的挖新坑。希望每個程式員手裡都是一個穩定的系統。

作者資訊:通木, Github 賬号 zhdd99 ,阿裡巴巴基礎設施事業部進階開發工程師,目前主要負責阿裡巴巴IDC監控系統。