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

問題初始

線上環境運行一段時間便出現cpu爆滿,致使服務不可用,沒法提供正常服務。shell

排查過程
  • 經過top命令查看進程使用情況

發現,進程號爲5525的進程cpu利用率很高。segmentfault

  • 隨即查看進程號爲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文件拿到本地來分析bash

  • 經過 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/…eclipse

點開查看
發現問題出如今阿里雲的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的連接 而後發現使用的地方oop

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調用的地方改爲了單例。修改完線上跑了一段日子,後來也沒有出現過這樣的問題。ui

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。

相關文章
相關標籤/搜索