面試官:你知道如何減少GC暫停時間嗎?

當面試官問到你,如何減少GC暫停時間的時間你知道怎麼回答嗎?

長時間的 GC 停頓對應用程序是不利的,它會影響服務的 SLA,進而導致糟糕的用戶體驗,並對核心應用程序的服務造成嚴重損害。因此,在本文中,我列出了導致長時間 GC 停頓的關鍵原因以及解決這些問題的可能的解決方案。

需要學習資料或者面試資料的可以私聊小七,或者在評論區留言

1. 高速率創建對象

如果你的應用程序的對象創建率很高,那麼為了跟上它,垃圾回收率也將會很高。高垃圾回收率也會增加 GC 停頓時間。因此,優化應用程序以創建更少的對象是減少長 GC 停頓的有效策略。這可能是一個耗時的工作,但百分百值得去做。為了優化應用程序中的對象創建速率,可以考慮先使用 Java 分析器來進行分析,例如 JProfiler,YourKit 或 JVisualVM,通過這些分析器可得出以下信息報告:

  • 創建了哪些對象?
  • 創建這些對象的速率是多少?
  • 它們在內存中佔用多少空間?
  • 誰在創建了它們?

始終嘗試去優化佔用最大內存量的對象。

提示: 如何計算對象創建速率

將你的 GC 日誌上傳到通用 GC 日誌分析器工具 GCeasy。該工具將報告對象創建率。在“對象統計信息”中將列出“平均創建率”。此項將報告對象創建率。力爭使該值保持較低。請參見下圖(摘自 GCeasy 生成的報告的目錄),顯示“平均創建速度”為 8.83 mb.sec

面試官:你知道如何減少GC暫停時間嗎?

2. 年輕代空間不足

當年輕代過小時,對象會過早地提升到老年代。從老年代收集垃圾比從年輕代收集垃圾要花費更多的時間。因此,增加年輕代的大小有可能減少長時間的 GC 停頓。可以通過設置兩個 JVM 參數之一來增加年輕一代的大小:

-Xmn :指定年輕代的大小。

-XX:NewRatio :指定年輕代相對於老年代的大小比例。例如,設置 -XX:NewRatio=2 表示年輕代與老年代之間的比率為 1:2。年輕代的大小將是整個堆的 1/3。因此,如果堆大小為 2 GB,則年輕代大小將為 2G / 3 = 667 MB。

3. 選擇 GC 算法

GC 算法對 GC 停頓時間有很大的影響。如果你是 GC 專家或打算成為一個(或你的團隊中的有人是 GC 專家),你可以調整 GC 參數配置以獲得最佳 GC 停頓時間。如果你沒有大量的 GC 的專業知識,那麼我建議使用 G1 GC 算法,因為它有自動調節的能力。在 G1 中,可以使用系統屬性 -xx:MaxGCPauseMillis來設置 GC 預期最大停頓時間。例如:

-XX:MaxGCPauseMillis=200

按照上面的例子,最大 GC 停頓時間設置為 200 毫秒。這是一個軟目標,JVM 將盡力實現它。

4. 進程使用了 Swap

有時由於物理內存不足(RAM),操作系統可能會將應用程序暫時不用的數據從內存交換出去。交換動作是非常昂貴的,因為它需要訪問磁盤,這比物理內存訪問要慢得多。

依我之見,在生產環境中,任何一個重要的應用程序都不應該交換。當進程使用了 Swap 時,GC 將需要很長的時間才能完成。

下面的腳本來自 StackOverflow (感謝作者),當執行腳本時,將顯示所有正在發生交換的進程。請確保你的應用程序進程沒有使用 Swap。

#!/bin/bash 
# Get current swap usage for all running processes
# Erik Ljungstrom 27/05/2011
# Modified by Mikko Rantalainen 2012-08-09
# Pipe the output to "sort -nk3" to get sorted output
# Modified by Marc Methot 2014-09-18
# removed the need for sudo
SUM=0
OVERALL=0
for DIR in `find /proc/ -maxdepth 1 -type d -regex "^/proc/[0-9]+"`
do
PID=`echo $DIR | cut -d / -f 3`
PROGNAME=`ps -p $PID -o comm --no-headers`

for SWAP in `grep VmSwap $DIR/status 2>/dev/null | awk '{ print $2 }'`
do
let SUM=$SUM+$SWAP
done
if (( $SUM > 0 )); then
echo "PID=$PID swapped $SUM KB ($PROGNAME)"
fi
let OVERALL=$OVERALL+$SUM
SUM=0
done
echo "Overall swap used: $OVERALL KB"

如果發現進程使用了 Swap 分區,則可以執行下列操作之一:

  • 分配更多的物理內存。
  • 減少在服務器上運行的進程的數量,以便它可以釋放內存(RAM)。
  • 減少應用程序的堆大小(我不建議這麼做,因為它會導致其他副作用。不過,它可能會解決你的問題)。

5. 調整 GC 線程數

對於 GC 日誌中報告的每個 GC 事件,會打印用戶、系統和實際執行時間。例如:

[Times: user=25.56 sys=0.35, real=20.48 secs]

如果在 GC 事件中,您始終注意到 real 時間並不顯著小於 user 時間,那麼它可能指示沒有足夠的 GC 線程。考慮增加 GC 線程數。假設 user 時間為 25s,並且將 GC 線程計數配置為 5,那麼 real 應該接近 5s(因為 25s/5=5s)。

警告:添加太多的 GC 線程將消耗大量 CPU,從而佔用應用程序的資源。因此,在增加 GC 線程數之前,需要進行充分的測試。

6. 後臺 I/O 活動

如果有大量的文件系統 I/O 活動(即發生大量的讀寫操作),也可能導致長時間的 GC 停頓。此繁重的文件系統 I/O 活動可能不是由應用程序引起的。可能是由於運行在同一服務器上的另一進程造成的。但它仍然會導致應用程序遭受長時間的 GC 停頓。

當有嚴重的 I/O 活動時,你會注意到 real 的時間明顯高於 user 的時間。例如:

[Times: user=0.20 sys=0.01, real=18.45 secs]

當這種情況發生時,以下是一些可能的解決方案:

  • 如果高 I/O 活動是由應用程序引起的,那麼優化它。
  • 消除在服務器上導致高 I/O 活動的進程。
  • 將應用程序移動到 I/O 活動較少的其他服務器。

提示: 如何監視 I/O 活動

在類 Unix系統 中,你可以使用的 SAR 命令(系統活動情況報告)監視 I/O 活動。例如:

sar -d -p 1

上面的命令每 1 秒會報告一次讀取/秒和寫入/秒的統計數據。有關 SAR 命令的更多細節,可以自行參閱相關資料。

7. System.gc() 調用

當調用 System.gc() or Runtime.getRuntime().gc() 方法時,它將導致 stop-the-world 的 Full GC。在 Full GC 期間,整個 JVM 被凍結(即在此期間不會執行任何用戶活動)。System.gc() 調用一般來源於以下情況:

  • 開發人員可能會顯式地調用 System.gc() 方法。
  • 使用的第三方庫、框架,有時甚至是應用程序服務器。其中任何一個都可能調用 System.gc() 方法。
  • 還可以通過使用 JMX 從外部工具(如 VisualVM)觸發。
  • 如果你的應用程序正在使用 RMI,那麼 RMI 會定期調用 System.gc() 。可以使用以下系統屬性配置此調用間隔:
-Dsun.rmi.dgc.server.gcInterval=n 
-Dsun.rmi.dgc.client.gcInterval=n

評估是否顯式調用 System.gc() 是絕對必要的。如果不需要,請把它刪掉。另一方面,可以通過傳遞 JVM 參數來強制禁用 System.gc() 調用 -XX:+DisableExplicitGC

提示:如何知道是否顯示調用了 System.gc()

將 GC 日誌上傳到通用 GC 日誌分析器工具GCeasy。此工具有一個名為 GCCauses的部分。如果由於System.gc()調用而觸發 GC 活動,則此部分將報告該情況。請看下圖(摘自 GCeasy 生成的報告目錄),顯示了 System.gc() 在這個應用程序的生命週期中被做了四次。

面試官:你知道如何減少GC暫停時間嗎?

警告:所有上述戰略只有經過徹底的測試和分析才能推廣到生產。所有策略可能不一定適用於你的應用程序。如果不當使用可能會導致負面的結果。

文章來源,,想知道的在評論裡說,私信我也可以

面試官:你知道如何減少GC暫停時間嗎?


分享到:


相關文章: