服務端性能問題排查及優化 ---內存問題分析

概述

內存問題主要體現在以下幾個方面

  • 內存溢出
  • 內存各區比例的配置不合理
  • 垃圾回收方式不合理

本文主要介紹內存溢出的排查方法。

  • 內存溢出
    首先,在發生內存溢出的時候,一定要第一時間抓內存的快照,有助於問題的分析。
    當內存溢出時需要先分析當前內存配置是否合理,而不是直接增大內存配置。

    • 一個無狀態的服務,在服務啓動和運行一段時間後,內存不會有太大的浮動。
    • 一個有狀態的服務,需要在開始階段計算需要緩存的數據,並配置合理的內存大小。
  • 垃圾回收問題 - 待補充
    Java GC方式和參數有很多,常用的有以下幾種模式。
    -XX:+UseParallelOldGC FGC時會停頓比較長的時間。
    -XX:+UseConcMarkSweepGC FGC停頓時間短,目前生產環境大多使用此配置。
    -XX:+UseG1GC

  • JVM參數配置的不合理導致的內存問題 - 待補充
    比如配置的太小或者太大,每個區的比例等。

高併發的系統中,服務端響應速度對服務本身影響很大,響應時間從1秒增加到2秒會導致內存裏的對象的生命週期變長,導致內存增大。生命週期變長還會導致原本在Young區能回收的對象提升到Old區,導致頻繁的FGC。

CMS方式可以減少停頓時間,但並不是沒有,內存分配不合理還是會導致一些問題,比如業務場景導致大部分對象會進入Old區,Old區分配比較大,頻繁的FGC也比較浪費資源,可以考慮重新分配Young區、Old區大小以及Eden:S0:S1比例,使對象儘量的在Young區回收。

可能導致內存溢出的情況

  • 內存配置過小或者不合理
    內存配置過小確實是內存溢出的一個原因,每個開發都應在應用上線前估算內存使用的一個大概數量級別,根據估算結果去配置一個合理的內存閾值(閾值?理會精神..),同時多關注內存的一個趨勢,避免因爲配置原因導致內存溢出。

  • 對象沒有釋放
    比較常見的問題,項目中經常出現多個對象的循環引用,最後某一個環節的對象hold住了所有的這些沒用的對象,導致垃圾回收不能及時的回收,進而導致內存溢出。

分析方法

首先保證出現問題的時候有東西去分析,可通過以下方法去保留事發現場。

  • 通過增加jvm啓動參數,使內存溢出時自動抓dump
    -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/outofmemory.dump
  • 通過jmap命令抓取應用的內存dump,分析內存是否有泄漏,使用介紹如下
    jmap -dump:live,format=b,file=heap.bin <pid>
    詳細使用方法可以使用jmap -h查看

分析dump信息

  • 通過jviaualvm加載抓取的dump,加載成功後切換到類的標籤,並按實例數量倒序排列,類似以下



    通常情況下,實例最多的爲基礎類型,比如byte、string、int等。儘量先從本項目的類來分析。
    通過對應用代碼的瞭解後,發現MemoryLeak數量異常。


  • 點擊MemoryLeak展開所有的MemoryLeak對象,通過右下角的引用查找對象的根節點,同時結合代碼去分析。

內存泄漏示例

Demo 代碼

MemoryLeak

public class MemoryLeak {
    private long id;
    private Byte[] bytes;

    public MemoryLeak() {
        id = new Date().getTime();
        int count = 256;
        bytes = new Byte[count];
        for (int i = 0; i < count; i++) {
            bytes[i] = (byte) i;
        }
    }
}

TestDemo

package demo.memory;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage;
import java.lang.management.RuntimeMXBean;
import java.util.List;

/**
 * VM options -Xms128m -Xmx128m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=outofmemory.bin
 *
 * Exception in thread "main" *** java.lang.instrument ASSERTION FAILED ***: "!errorOutstanding" with message can't create byte arrau at JPLISAgent.c line: 813
 * java.lang.OutOfMemoryError: Java heap space
 *  at demo.memory.MemoryLeak.<init>(MemoryLeak.java:22)
 *  at demo.memory.TestDemo.main(TestDemo.java:44)
 *
 */
public class TestDemo {
    static MemoryLeak[] memoryLeakArray;
    private static int processId = 0;

    public static void main(String[] args) throws Exception {
        processId = getProcessID();

        MemoryMXBean memorymbean = ManagementFactory.getMemoryMXBean();
        MemoryUsage usage = memorymbean.getHeapMemoryUsage();
        System.out.println("INIT HEAP: " + usage.getInit());
        System.out.println("MAX HEAP: " + usage.getMax());
        System.out.println("USE HEAP: " + usage.getUsed());
        List<String> inputArguments = ManagementFactory.getRuntimeMXBean().getInputArguments();
        System.out.println("===================JVM OPTIONS=============== ");
        System.out.println(inputArguments);

        int sec = 30;
        int count = 1024;
        memoryLeakArray = new MemoryLeak[count * sec];
        for (int s = 0; s < sec; s++) {
            for (int i = 0; i < count; i++) {
                memoryLeakArray[s * count + i] = new MemoryLeak();
            }
            Thread.sleep(1000);
            System.out.println("sec:" + s);
            printGcStat();
            //System.in.read();
        }

        while (true) {
            Thread.sleep(1000);
        }

    }

    /**
     * 打印GC性信息
     */
    public static void printGcStat() {
        Process process;
        try {
            process = Runtime.getRuntime().exec("jstat -gcutil " + processId);
            BufferedReader input = new BufferedReader(new InputStreamReader(process.getInputStream()));
            String line;
            while ((line = input.readLine()) != null) {
                System.out.println(line);
            }
            input.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 獲取進程ID
     * @return
     */
    public static int getProcessID() {
        RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean();
        return Integer.valueOf(runtimeMXBean.getName().split("@")[0]);
    }

}

執行結果

demo.memory.TestDemo
INIT HEAP: 134217728
MAX HEAP: 128974848
USE HEAP: 4091336
===================JVM OPTIONS=============== 
[-Xms128m, -Xmx128m, -XX:+HeapDumpOnOutOfMemoryError, -XX:HeapDumpPath=outofmemory.bin, -javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=57241:/Applications/IntelliJ IDEA.app/Contents/bin, -Dfile.encoding=UTF-8]
sec:0
  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT   
  0.00   0.00  44.05   0.00  17.29  19.76      0    0.000     0    0.000    0.000
sec:1
  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT   
  0.00   0.00  70.05   0.00  17.29  19.76      0    0.000     0    0.000    0.000
sec:2
  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT   
  0.00   0.00  96.06   0.00  17.29  19.76      0    0.000     0    0.000    0.000
sec:3
  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT   
  0.00  99.53  21.34  25.99  93.32  83.13      1    0.023     0    0.000    0.023
sec:4
  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT   
  0.00  99.53  46.59  25.99  93.32  83.13      1    0.023     0    0.000    0.023
sec:5
  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT   
  0.00  99.53  69.88  25.99  93.32  83.13      1    0.023     0    0.000    0.023
sec:6
  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT   
  0.00  99.53  95.10  25.99  93.32  83.13      1    0.023     0    0.000    0.023
sec:7
  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT   
  0.00   0.00  21.62  69.21  93.33  83.13      2    0.061     1    0.525    0.586
sec:8
  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT   
  0.00   0.00  47.12  69.21  93.33  83.13      2    0.061     1    0.525    0.586
sec:9
  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT   
  0.00   0.00  72.62  69.21  93.33  83.13      2    0.061     1    0.525    0.586
sec:10
  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT   
  0.00   0.00  98.11  69.21  93.33  83.13      2    0.061     1    0.525    0.586
sec:11
  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT   
  0.00   0.00  38.39  99.82  93.37  83.13      2    0.061     2    0.672    0.733
sec:12
  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT   
  0.00   0.00  63.89  99.82  93.37  83.13      2    0.061     2    0.672    0.733
sec:13
  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT   
  0.00   0.00  89.39  99.82  93.37  83.13      2    0.061     2    0.672    0.733
java.lang.OutOfMemoryError: GC overhead limit exceeded
Dumping heap to outofmemory.bin ...
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
Heap dump file created [205085894 bytes in 1.296 secs]
    at demo.memory.MemoryLeak.<init>(MemoryLeak.java:23)
    at demo.memory.TestDemo.main(TestDemo.java:44)

Process finished with exit code 1

JVisualVM分析過程

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章