国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

記一次 JAVA 的內存泄露分析

Tecode / 794人閱讀

摘要:展示如下場景再現經過分析,最后我們定位到是使用產生的內存泄露問題。下面通過一個,來簡單講下具體內存泄露的原因。這一次的內存泄露問題算是解決了。總結關于內存泄露問題在第一次排查時,往往是有點不知所措的。

記一次 JAVA 的內存泄露分析

摘要:本文屬于原創,歡迎轉載,轉載請保留出處:https://github.com/jasonGeng88/blog

當前環境

jdk == 1.8

httpasyncclient == 4.1.3

代碼地址

git 地址:https://github.com/jasonGeng88/java-network-programming

背景

前不久,上線了一個新項目,這個項目是一個壓測系統,可以簡單的看做通過回放詞表(http請求數據),不斷地向服務發送請求,以達到壓測服務的目的。在測試過程中,一切還算順利,修復了幾個小bug后,就上線了。在上線后給到第一個業務方使用時,就發現來一個嚴重的問題,應用大概跑了10多分鐘,就收到了大量的 Full GC 的告警。

針對這一問題,我們首先和業務方確認了壓測的場景內容,回放的詞表數量大概是10萬條,回放的速率單機在 100qps 左右,按照我們之前的預估,這遠遠低于單機能承受的極限。按道理是不會產生內存問題的。

線上排查

首先,我們需要在服務器上進行排查。通過 JDK 自帶的 jmap 工具,查看一下 JAVA 應用中具體存在了哪些對象,以及其實例數和所占大小。具體命令如下:

jmap -histo:live `pid of java`

# 為了便于觀察,還是將輸出寫入文件
jmap -histo:live `pid of java` > /tmp/jmap00

經過觀察,確實發現有對象被實例化了20多萬,根據業務邏輯,實例化最多的也就是詞表,那也就10多萬,怎么會有20多萬呢,我們在代碼中也沒有找到對此有顯示聲明實例化的地方。至此,我們需要對 dump 內存,在離線進行進一步分析,dump 命令如下:

jmap -dump:format=b,file=heap.dump `pid of java`
離線分析

從服務器上下載了 dump 的 heap.dump 后,我們需要通過工具進行深入的分析。這里推薦的工具有 mat、visualVM。

我個人比較喜歡使用 visualVM 進行分析,它除了可以分析離線的 dump 文件,還可以與 IDEA 進行集成,通過 IDEA 啟動應用,進行實時的分析應用的CPU、內存以及GC情況(GC情況,需要在visualVM中安裝visual GC 插件)。工具具體展示如下(這里僅僅為了展示效果,數據不是真的):

當然,mat 也是非常好用的工具,它能幫我們快速的定位到內存泄露的地方,便于我們排查。
展示如下:

場景再現

經過分析,最后我們定位到是使用 httpasyncclient 產生的內存泄露問題。httpasyncclient 是 Apache 提供的一個 HTTP 的工具包,主要提供了 reactor 的 io 非阻塞模型,實現了異步發送 http 請求的功能。

下面通過一個 Demo,來簡單講下具體內存泄露的原因。

httpasyncclient 使用介紹:

maven 依賴


    org.apache.httpcomponents
    httpasyncclient
    4.1.3

HttpAsyncClient 客戶端

public class HttpAsyncClient {

    private CloseableHttpAsyncClient httpclient;

    public HttpAsyncClient() {
        httpclient = HttpAsyncClients.createDefault();
        httpclient.start();
    }

    public void execute(HttpUriRequest request, FutureCallback callback){
        httpclient.execute(request, callback);
    }

    public void close() throws IOException {
        httpclient.close();
    }

}
主要邏輯:

Demo 的主要邏輯是這樣的,首先創建一個緩存列表,用來保存需要發送的請求數據。然后,通過循環的方式從緩存列表中取出需要發送的請求,將其交由 httpasyncclient 客戶端進行發送。

具體代碼如下:

public class ReplayApplication {

    public static void main(String[] args) throws InterruptedException {

         //創建有內存泄露的回放客戶端
        ReplayWithProblem replay1 = new ReplayWithProblem();
        
         //加載一萬條請求數據放入緩存
        List cache1 = replay1.loadMockRequest(10000);
        
        //開始循環回放
        replay1.start(cache1);

    }
}
回放客戶端實現(內存泄露):

這里以回放百度為例,創建10000條mock數據放入緩存列表。回放時,以 while 循環每100ms 發送一個請求出去。具體代碼如下:

public class ReplayWithProblem {

    public List loadMockRequest(int n){
    
        List cache = new ArrayList(n);
        for (int i = 0; i < n; i++) {
            HttpGet request = new HttpGet("http://www.baidu.com?a="+i);
            cache.add(request);
        }
        return cache;
        
    }

    public void start(List cache) throws InterruptedException {

        HttpAsyncClient httpClient = new HttpAsyncClient();
        int i = 0;

        while (true){

            final HttpUriRequest request = cache.get(i%cache.size());
            httpClient.execute(request, new FutureCallback() {
                public void completed(final HttpResponse response) {
                    System.out.println(request.getRequestLine() + "->" + response.getStatusLine());
                }

                public void failed(final Exception ex) {
                    System.out.println(request.getRequestLine() + "->" + ex);
                }

                public void cancelled() {
                    System.out.println(request.getRequestLine() + " cancelled");
                }

            });
            i++;
            Thread.sleep(100);
        }
    }

}
內存分析:

啟動 ReplayApplication 應用(IDEA 中安裝 VisualVM Launcher后,可以直接啟動visualvm),通過 visualVM 進行觀察。

啟動情況:

visualVM 中前后3分鐘的內存對象占比情況:


說明:$0代表的是對象本身,$1代表的是該對象中的第一個內部類。所以ReplayWithProblem$1: 代表的是ReplayWithProblem類中FutureCallback的回調類。

從中,我們可以發現 FutureCallback 類會被不斷的創建。因為每次異步發送 http 請求,都是通過創建一個回調類來接收結果,邏輯上看上去也正常。不急,我們接著往下看。

visualVM 中前后3分鐘的GC情況:


從圖中看出,內存的 old 在不斷的增長,這就不對了。內存中維持的應該只有緩存列表的http請求體,現在在不斷的增長,就有說明了不斷的有對象進入old區,結合上面內存對象的情況,說明了 FutureCallback 對象沒有被及時的回收。

可是該回調匿名類在 http 回調結束后,引用關系就沒了,在下一次 GC 理應被回收才對。我們通過對 httpasyncclient 發送請求的源碼進行跟蹤了一下后發現,其內部實現是將回調類塞入到了http的請求類中,而請求類是放在在緩存隊列中,所以導致回調類的引用關系沒有解除,大量的回調類晉升到了old區,最終導致 Full GC 產生。

核心代碼分析:

代碼優化

找到問題的原因,我們現在來優化代碼,驗證我們的結論。因為List cache1中會保存回調對象,所以我們不能緩存請求類,只能緩存基本數據,在使用時進行動態的生成,來保證回調對象的及時回收。

代碼如下:

public class ReplayApplication {

    public static void main(String[] args) throws InterruptedException {

        ReplayWithoutProblem replay2 = new ReplayWithoutProblem();
        List cache2 = replay2.loadMockRequest(10000);
        replay2.start(cache2);

    }
}
public class ReplayWithoutProblem {

    public List loadMockRequest(int n){
        List cache = new ArrayList(n);
        for (int i = 0; i < n; i++) {
            cache.add("http://www.baidu.com?a="+i);
        }
        return cache;
    }

    public void start(List cache) throws InterruptedException {

        HttpAsyncClient httpClient = new HttpAsyncClient();
        int i = 0;

        while (true){

            String url = cache.get(i%cache.size());
            final HttpGet request = new HttpGet(url);
            httpClient.execute(request, new FutureCallback() {
                public void completed(final HttpResponse response) {
                    System.out.println(request.getRequestLine() + "->" + response.getStatusLine());
                }

                public void failed(final Exception ex) {
                    System.out.println(request.getRequestLine() + "->" + ex);
                }

                public void cancelled() {
                    System.out.println(request.getRequestLine() + " cancelled");
                }

            });
            i++;
            Thread.sleep(100);
        }
    }

}
結果驗證

啟動情況:

visualVM 中前后3分鐘的內存對象占比情況:


visualVM 中前后3分鐘的GC情況:


從圖中,可以證明我們得出的結論是正確的。回調類在 Eden 區就會被及時的回收掉。old 區也沒有持續的增長情況了。這一次的內存泄露問題算是解決了。

總結

關于內存泄露問題在第一次排查時,往往是有點不知所措的。我們需要有正確的方法和手段,配上好用的工具,這樣在解決問題時,才能游刃有余。當然對JAVA內存的基礎知識也是必不可少的,這時你定位問題的關鍵,不然就算工具告訴你這塊有錯,你也不能定位原因。

最后,關于 httpasyncclient 的使用,工具本身是沒有問題的。只是我們得了解它的使用場景,往往產生問題多的,都是使用的不當造成的。所以,在使用工具時,對于它的了解程度,往往決定了出現 bug 的機率。

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/67681.html

相關文章

  • 一次OkHttpClient導致線程過多排查

    摘要:首先先解讀下這個報警內容,原因活躍線程數過多,是監聽的端口號用來獲取虛擬機各項信息,代表著此時的線程數,是設置的報警閾值。 前言 前天,一位21世紀的好好青年正在工位上默念社會主義大法好的時候,釘釘上又報警了(公司項目接入了open-faclon監控,指標不正常會報警給釘釘的機器人),無奈默默流淚揮手告別社會主義大法開始定位線上問題。 報警內容 首先我們先來看下報警信息,為防止泄露公...

    tianyu 評論0 收藏0
  • 一次JVM調優

    摘要:現象登入生產環境,使用命令因為這時候并沒有打的,所以只能觀察現象。其他的可以根據這個類推,是內純的占用量。 前言 我們的游戲上線之初,經常有玩家反饋卡,或者有網絡延遲等現象,造成用戶流失等現象,這時候我就想到是不是可能是之前的jvm配置有問題,或者存在內存泄露等問題。 現象 登入生產環境,使用命令,因為這時候并沒有打gc的log,所以只能觀察現象。 jstat -gcutil 270...

    sugarmo 評論0 收藏0
  • 一次線上頻繁FGC事件和解決方式

    摘要:直接顯示了一個疑似內存泄漏的問題。然后分析文件給出的信息,發現一個叫的類。文件里面說的內存泄漏的大概的意思就是說,這個類里面的存放的東西太多了,爆掉了。修改了代碼將調用的地方改成了單例。修改完線上跑了一段日子,后來也沒有出現過這樣的問題。 問題描述: ????早上去公司上班,突然就郵件一直報警,接口報異常,然后去查服務器的運行情況,發現java的cpu爆了.接著就開始排查問題 問題解決...

    Alliot 評論0 收藏0

發表評論

0條評論

最新活動
閱讀需要支付1元查看
<