天天看點

記錄一次線上Full GC問題排查

問題初始

線上環境運作一段時間便出現cpu爆滿,導緻服務不可用,無法提供正常服務。

排查過程

  • 通過top指令檢視程序使用狀況

發現,程序号為5525的程序cpu使用率很高。

  • 随即檢視程序号為5525的程序中的線程情況,使用指令:
top -H -p 5525
複制代碼
           

發現前四個線程cpu占用率很高,而且持有cpu時間很長,是以檢視該線程是誰在使用,因為該線程号顯示為十進制,是以首先将其轉化為十六進制

printf "%x\n" 5530
複制代碼
           

接着使用 jstack 指令檢視該線程是誰持有

jstack 5525 | grep 159a
複制代碼
           

發現竟然是GC占用,cpu使用率很高,而且持有cpu很長時間。繼續-發現是GC的問題,接下來檢視一下GC的狀态資訊

  • 通過 jstat檢視 gc 狀态(沒10秒gc狀态)
jstat -gcutil 5525 2000 10
複制代碼
           

可以看出記憶體的年輕代和年老帶的使用率都達到了驚人的100%。FGC的次數也特别多,并且在不斷飙升。可以推斷出 程式肯定是在哪裡的實作有問題,需要重點檢視大對象或者異常多的對象資訊。此時可以生成headdump檔案拿到本地來分析

  • 通過 jmap 指令生成dump檔案,down下來進行分析
jmap -dump:format=b,file=dump.bin 5525
複制代碼
           
  • 通過 jstack 指令生成堆棧資訊
jstack -l 5525 >> jstack.out
複制代碼
           

使用eclipse的mat工具分析dump檔案,結果如下

使用方法及mat概念:www.lightskystreet.com/2015/09/01/…

點開檢視 發現問題出現在阿裡雲的oss中 sdk 的 com.aliyun.oss.common.comm.IdleConnectionReaper 類中 根據官方說明:

www.alibabacloud.com/help/zh/doc… 然後分析代碼,哪裡建立的client,建立client的動作在哪裡使用。
private OSSClient getOSSClient(){
		OSSClient	client	= null;
		client	= new OSSClient(endpoint, accessKeyId, accessKeySecret);
		return client;
	}
複制代碼
           

結果發現,每次都會建立一個新的對象,建立一個新的oss的連結 然後發現使用的地方

public boolean objectExist(String path) {
    OSSClient ossClient = getOSSClient();
    return ossClient.doesObjectExist(bucketName, path);
}
複制代碼
           

業務中涉及到檔案存儲的一律都是oss存儲,而且由于曆史原因,有些路徑不是oss路徑,而是本地伺服器路徑,是以在傳回檔案連結時,會進行判斷,

public static String getResourceUrl(String url) {
    if (url != null && !url.trim().isEmpty()) {
    	if (url.trim().toLowerCase().startsWith("http")) {
    		return url.trim();
    	}
    	// 如果oss路徑下能查到,則從oss查
    	if (url.startsWith("/")) {
    		url = url.substring(1);
    	}
    	// 注釋掉去oss驗證的步驟,不會有性能損耗
    	boolean objectExist = ossHelper.objectExist(url);
    	if (objectExist) {
    		return XTools.getUrl(getOssServer(), url);
    	}
    	 // 否則從本地查
    	return XTools.getUrl(getResServer(), url);
    }
    return "";
	}
複制代碼
           

分析一下,在判斷oss路徑是否存在的時候,也就是調用boolean objectExist = ossHelper.objectExist(url);方法時,每次都會建立一個oss的連結,而我們的檔案是很多的,是以每個檔案傳回通路連結的時候都會調用這個方法,是以建立的oss的連結是巨多的!!! 查詢資料發現,如下網友也中招:

是以大概原因找到了,就是ossClient的連結太多了,扛不住了,是以一直在進行FGC,導緻服務不可用了,最後找到相關的代碼,發現有個小方法裡面在每次上傳或者下載下傳的時候,都會去建立一個ossClient。修改了代碼将ossClient調用的地方改成了單例。修改完線上跑了一段日子,後來也沒有出現過這樣的問題。

segmentfault.com/a/119000001…

代碼優化

由此得出結論:優化ossHelper類中建立client的方法。并且在初始化 ossHelper 的 bean的時候,初始化建立client的連接配接,交給Spring管理。

private static OSSClient ossClient;
private OSSClient getOSSClient(){
    if(ossClient == null){
	synchronized (this){
	  ossClient = new OSSClient(endpoint, accessKeyId, accessKeySecret);
			   }
		        }
	return ossClient;
	}
複制代碼
           

xml:

<bean id="ossHelper" class="com.ybl.crm.common.oss.OssHelper" init-method="getOSSClient">
    <constructor-arg index="0" value="${oss.endpoint}"/>
    <constructor-arg index="1" value="${oss.access.key.id}"/>
    <constructor-arg index="2" value="${oss.access.key.secret}"/>
    <constructor-arg index="3" value="${oss.bucket.name}"/>
    <constructor-arg index="4" value="${oss.server}"/>
</bean>
複制代碼
           

注解方式:@Component聲明元件,@PostConstruct聲明需要随元件初始化的方法.

jmeter壓測驗證

經jmeter壓測驗證

samples:請求數
average:平均耗時,機關:毫秒
error:錯誤率
複制代碼
           
// 此處是每秒啟動20個線程,每個線程發送10個請求
Number of threads(users) : 使用者數,即線程數
Ramp-up period(in seconds) : 線程多長時間全部啟動起來
Loop Count : 每個線程發送的請求數
複制代碼
           

兩千個請求,平均每個請求2.3秒,請求很快,錯誤率很低。 原先寫法的話,當請求到達一定次數,伺服器服務已經無法響應,導緻錯誤率上升!而且cpu一直爆滿,其内部維護的client滿了的話,會不斷的觸發GC,是以就導緻頻繁GC。

轉載于:https://juejin.im/post/5caed4d36fb9a068985fa527