OSSClient の不適切な使用によって引き起こされた OOM のトラブルシューティング プロセスを記録する

最初のリリース: 公開アカウント「Zhao Xiake

序文

最近、オンラインの比較的マイナーなプロジェクトで OOM が発生しました。幸いなことに、このプロジェクトは一部のオフライン タスク処理を行うだけです。OOM はオンライン ビジネスには影響しません。トラブルシューティング プロセスの記録は次のとおりです。

ダンプログビュー

プロジェクト構成の主な JVM パラメータ設定は次のとおりです。

-Xmx5120m -XX:+PreserveFramePointer -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/usr/local/update/heap_trace.log -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/update/dump.log 

最大ヒープ メモリは 5G に設定されており、GC ログは OOM 後にエクスポートされたメモリを記録するように構成されています。まず、OOM のエクスポートされたメモリ スナップショットを見てみましょう。dump.log には実際には 5GB があります。最初の判断は、そこにあるということです。メモリリークです。

次に、heap_trace.log の GC ログを確認したところ、最後の数回の GC には 0.02 秒かかり、多くのメモリが解放されていませんでした。メモリ リークが発生しているはずです。

2023-09-18T09:58:28.259+0800: 234057.213: [GC (Allocation Failure) [PSYoungGen: 438400K->7648K(441344K)] 763838K->333358K(961024K), 0.0140907 secs] [Times: user=0.04 sys=0.00, real=0.02 secs]  
2023-09-18T10:01:33.925+0800: 234242.879: [GC (Allocation Failure) [PSYoungGen: 436704K->7344K(441856K)] 762414K->333326K(961536K), 0.0134861 secs] [Times: user=0.04 sys=0.00, real=0.01 secs]  
2023-09-18T10:04:16.426+0800: 234405.380: [GC (Allocation Failure) [PSYoungGen: 437424K->8832K(441856K)] 763406K->335022K(961536K), 0.0147276 secs] [Times: user=0.05 sys=0.00, real=0.01 secs]  
2023-09-18T10:06:30.923+0800: 234539.877: [GC (Allocation Failure) [PSYoungGen: 438912K->11520K(442368K)] 765102K->338158K(962048K), 0.0202829 secs] [Times: user=0.06 sys=0.00, real=0.02 secs]  
2023-09-18T10:08:27.655+0800: 234656.609: [GC (Allocation Failure) [PSYoungGen: 442112K->12272K(442880K)] 768750K->340510K(962560K), 0.0216111 secs] [Times: user=0.06 sys=0.00, real=0.02 secs]  
2023-09-18T10:11:37.773+0800: 234846.727: [GC (Allocation Failure) [PSYoungGen: 442864K->12000K(445440K)] 771102K->340918K(965120K), 0.0243473 secs] [Times: user=0.06 sys=0.00, real=0.02 secs]  
2023-09-18T10:14:56.925+0800: 235045.879: [GC (Allocation Failure) [PSYoungGen: 443616K->8192K(445952K)] 772534K->337110K(965632K), 0.0152287 secs] [Times: user=0.04 sys=0.00, real=0.01 secs]  
2023-09-18T10:17:49.358+0800: 235218.312: [GC (Allocation Failure) [PSYoungGen: 439808K->8432K(445952K)] 768726K->337790K(965632K), 0.0151303 secs] [Times: user=0.05 sys=0.00, real=0.02 secs]  
2023-09-18T10:20:51.356+0800: 235400.310: [GC (Allocation Failure) [PSYoungGen: 441072K->8976K(446464K)] 770430K->338470K(966144K), 0.0159285 secs] [Times: user=0.05 sys=0.00, real=0.02 secs]  
2023-09-18T10:24:05.395+0800: 235594.349: [GC (Allocation Failure) [PSYoungGen: 441616K->9504K(446464K)] 771110K->339358K(966144K), 0.0219962 secs] [Times: user=0.05 sys=0.00, real=0.02 secs]  
2023-09-18T10:26:48.374+0800: 235757.328: [GC (Allocation Failure) [PSYoungGen: 443168K->11680K(446976K)] 773022K->341950K(966656K), 0.0195554 secs] [Times: user=0.05 sys=0.00, real=0.02 secs]

Jprofiler を使用してダンプ ファイルを分析する

JProFiler を使用してダンプ ファイルを開くと、HashMap$Node に実際には 1GB があることがわかります。

Node を選択すると、3,000 万を超えるオブジェクトがあることがわかります。

[受信参照のマージ] を選択し、Node オブジェクトの参照チェーンを段階的に拡張し、最終的に Node を参照する OSSClient オブジェクトがあることを発見しました。

このビジネスはファイルのアップロードに Alibaba Cloud OSS を多用していると考えたところ、OSSClient を使用するコードを見つけました。ファイルはエージェントで毎回アップロードされますが、毎回新しいローカル変数があってもメモリを消費することはありませnew OSSClient()ん漏れ?

そこで、DefaultServiceClient のソースコードを見てみると、OSSClient 作成時に呼び出されていることがわかります。createHttpClientConnectionManager

    public DefaultServiceClient(ClientConfiguration config) {
    
    
        super(config);
        this.connectionManager = createHttpClientConnectionManager();
        this.httpClient = createHttpClient(this.connectionManager);
        RequestConfig.Builder requestConfigBuilder = RequestConfig.custom();    
        

現在の接続を管理するためcreateHttpClientConnectionManagerに使用されますIdleConnectionReaper

    protected HttpClientConnectionManager createHttpClientConnectionManager() {
    
    
        SSLContext sslContext = null;
      if (config.isUseReaper()) {
    
    
            IdleConnectionReaper.setIdleConnectionTime(config.getIdleConnectionTime());
            IdleConnectionReaper.registerConnectionManager(connectionManager);
        }
        return connectionManager;
    }

すべての HTTP 接続が次を使用して保存されていることがわかりIdleConnectionReaper.registerConnectionManagerます。ArrayList

public final class IdleConnectionReaper extends Thread {
    
    
    private static final int REAP_INTERVAL_MILLISECONDS = 5 * 1000;
    private static final ArrayList<HttpClientConnectionManager> connectionManagers = new ArrayList<HttpClientConnectionManager>();

    private static IdleConnectionReaper instance;

    private static long idleConnectionTime = 60 * 1000;

    private volatile boolean shuttingDown;

    private IdleConnectionReaper() {
    
    
        super("idle_connection_reaper");
        setDaemon(true);
    }

    public static synchronized boolean registerConnectionManager(HttpClientConnectionManager connectionManager) {
    
    
        if (instance == null) {
    
    
            instance = new IdleConnectionReaper();
            instance.start();
        }
        return connectionManagers.add(connectionManager);
    }

OSSClient がメソッドを提供していることがわかりますshutdown。新しい OSSClint が使用されなくなった場合は、shutdown を呼び出して接続を解放する必要があります。これにより、接続されている接続が connectionManagers から削除されます。確かに、これはコードの不適切な使用によって引き起こされる OOM です。

    @Override
    public void shutdown() {
    
    
        IdleConnectionReaper.removeConnectionManager(this.connectionManager);
        this.connectionManager.shutdown();
    }  
  public static synchronized boolean removeConnectionManager(HttpClientConnectionManager connectionManager) {
    
    
        boolean b = connectionManagers.remove(connectionManager);
        if (connectionManagers.isEmpty())
            shutdown();
        return b;
    }

Alibaba Cloud 公式 Web サイトでも同じ問題を見つけました。
画像.png

解決:

  1. アプリケーション内で OSSClient を複数回インスタンス化することを避けるために、OSSClient インスタンスをシングルトン モードとして定義します。
  2. OSSClient.shutdown() メソッドを使用して、OSSClient インスタンスを閉じ、リソースを解放します。
  3. try-finally ブロックを使用し、finally で OSSClient.shutdown() メソッドを呼び出します。
  4. アプリケーションで OSSClient を使用する場合は、完了後に OSSClient インスタンスを必ず閉じてください。

地元の再生産

私たちのローカル有効化プロジェクトでは、Jprofiler を使用して JVM に接続します。

この関数は外部へのインターフェイスを提供するため、このインターフェイスを要求し続ける for ループを作成し、メモリの変化を観察しました。6 分間実行した後、使用可能なメモリは 0 になりました。

サーバーは OOM も報告しました:

問題を解く

ファクトリ パターンを使用してコードを書き換えます。

    public static final Map<String, OSSClient> map = new ConcurrentHashMap<>();
    public static OSSClient getClient(String endpoint, String accessKey, String accessSecret) {
        if (!map.containsKey(accessKey)) {
            OSSClient client = new OSSClient(endpoint, accessKey, accessSecret);
            map.put(accessKey, client);
        }
        return map.get(accessKey);
    }

元のコードを置き換えます。

  OSSClient client = AliyunUtil.getClient(endpoint, getAccessKey(), getAccessSecret());

再度テストしたところ、各 GC がメモリを非常にうまく解放していることがわかり、6 分間実行した後でもメモリ使用量は 200M を超えず、問題は完全に解決されました。

要約する

この記事では、Alibaba Cloud OSSClient の不適切な使用によって引き起こされるオンライン OOM プロセスをトラブルシューティングするための Jprofiler の使用について紹介します。この問題は主に、コードを作成するときに OSSClient の手動シャットダウンに注意を払わないことが原因です。幸いなことに、この問題はコア ビジネスには発生しません。今後、他の人が提供するツールを使用する場合は、公式がそのツールをどのように使用しているかを詳しく読み、ソースコードを読んで、将来同様の問題が発生しないようにする必要があります。

おすすめ

転載: blog.csdn.net/whzhaochao/article/details/132992028