web-dev-qa-db-ja.com

Java単体テスト:メソッド呼び出しのメモリフットプリントを測定する方法

いくつかのコレクションを操作して、重い処理を行うクラスがあると仮定します。私がやりたいのは、そのような操作がメモリ不足にならないようにすることであり、さらに良いことに、使用可能なメモリ量のしきい値を設定することです。

class MyClass()
{
   public void myMethod()
   {
      for(int i=0; i<10000000; i++)
      {
         // Allocate some memory, may be several collections
      }
   }
}

class MyClassTest
{
   @Test
   public void myMethod_makeSureMemoryFootprintIsNotBiggerThanMax()
   {
      new MyClass().myMethod(); 
      // How do I measure amount of memory it may try to allocate?
   }
}

これを行うための正しいアプローチは何ですか?または、これは不可能/実行不可能ですか?

31
Sergey Makarov

私はいくつかのオプションを考えることができます:

  • メソッドが必要とするメモリ量をマイクロベンチマーク(つまり jmh )で確認します。
  • 発見的推定に基づいた配分戦略の構築。クラスサイズの見積もりを実装するオープンソースソリューションがいくつかあります。つまり、 ClassSize です。非常に簡単な方法は、めったに使用されないオブジェクトを解放するキャッシュ(つまり、グアバのキャッシュ)を利用することです。 @EnnoShiojiが述べたように、グアバのキャッシュにはメモリベースの追い出しポリシーがあります。

メモリをカウントする独自のベンチマークテストを作成することもできます。アイデアは

  1. シングルスレッドを実行します。
  2. 割り当てるオブジェクトを保存する新しい配列を作成します。したがって、これらのオブジェクトはGCの実行中に収集されません。
  3. System.gc()memoryBefore = runtime.totalMemory() - runtime.freeMemory()
  4. オブジェクトを割り当てます。それらを配列に入れます。
  5. System.gc()memoryAfter = runtime.totalMemory() - runtime.freeMemory()

これは、 軽量マイクロベンチマークツール で使用した手法で、バイト精度でメモリ割り当てを測定できます。

16
Andrey Chaschev

プロファイラ(例:JProfiler)を使用して、クラスによるメモリ使用量を表示できます。または、Areoがどのように言及したか、単にメモリ使用量を出力します。

    Runtime runtime = Runtime.getRuntime();
    long usedMemoryBefore = runtime.totalMemory() - runtime.freeMemory();
    System.out.println("Used Memory before" + usedMemoryBefore);
        // working code here
    long usedMemoryAfter = runtime.totalMemory() - runtime.freeMemory();
    System.out.println("Memory increased:" + (usedMemoryAfter-usedMemoryBefore));
13
pasha701

現在のメモリ使用量を測定するには:

Runtime.getRuntime().freeMemory()Runtime.getRuntime().totalMemory()

次に例を示します。 OSレベルのシステム情報を取得

しかし、この測定は正確ではありませんが、多くの情報を提供できます。もう1つの問題は、予測不能なGCにあります。

2
Areo

同様のことを行うNettyの例を次に示します。 MemoryAwareThreadPoolExecutor 。グアバの キャッシュクラス には、サイズベースのエビクションもあります。これらのソースを見て、彼らがしていることをコピーすることができます。特に、Nettyの仕組みは次のとおりです オブジェクトサイズの推定 。基本的に、メソッドで生成するオブジェクトのサイズを推定し、カウントを保持します。

全体的なメモリ情報(使用可能なヒープ/使用量など)を取得すると、メソッドに割り当てるメモリ使用量を決定するのに役立ちますが、個々のメソッド呼び出しで使用されたメモリ量を追跡することはできません。

そうは言っても、これが正当に必要になることは非常にまれです。ほとんどの場合、特定のポイントに存在できるオブジェクトの数を制限することで(たとえば、制限されたキューを使用することで)メモリ使用量を制限するだけで十分であり、実装ははるかに簡単です。

2
Enno Shioji

この質問は、Javaが処理中に多くの短命オブジェクトを割り当てることができ、後でガベージコレクション中に収集される方法のため、少し注意が必要です。受け入れられた答えでは、複数のSystem.gc()呼び出しでループ構造を導入しても、メソッド呼び出しの間にガベージコレクションが実行される可能性があります。

より良い方法は、代わりに https://cruftex.net/2017/03/28/The-6-Memory-Metrics-You-Should-Track-in-Your- Java-Benchmarks.htmlSystem.gc()がトリガーされますが、報告されたGCカウントが増加するのを待ちます:

long getGcCount() {
    long sum = 0;
    for (GarbageCollectorMXBean b : ManagementFactory.getGarbageCollectorMXBeans()) {
        long count = b.getCollectionCount();
        if (count != -1) { sum += count; }
    }
    return sum;
}

long getReallyUsedMemory() {
    long before = getGcCount();
    System.gc();
    while (getGcCount() == before);
    return getCurrentlyAllocatedMemory();
}

long getCurrentlyAllocatedMemory() {
    final Runtime runtime = Runtime.getRuntime();
    return (runtime.totalMemory() - runtime.freeMemory()) / (1024 * 1024);
}

これは、特定の時間にコードによって実際に割り当てられたメモリの概算のみを提供しますが、値は通常、通常関心のあるものにはるかに近くなります。

0

これは、別のスレッドでメモリ使用量を実行するサンプルコードです。 GCはプロセスの実行中にいつでもトリガーできるため、これはメモリ使用量を毎秒記録し、使用された最大メモリを報告します。

runnableは測定が必要な実際のプロセスであり、runTimeSecsはプロセスが実行されると予想される時間です。これは、メモリを計算するスレッドが実際のプロセスの前に終了しないようにするためです。

public void recordMemoryUsage(Runnable runnable, int runTimeSecs) {
    try {
        CompletableFuture<Void> mainProcessFuture = CompletableFuture.runAsync(runnable);
        CompletableFuture<Void> memUsageFuture = CompletableFuture.runAsync(() -> {


            long mem = 0;
            for (int cnt = 0; cnt < runTimeSecs; cnt++) {
                long memUsed = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
                mem = memUsed > mem ? memUsed : mem;
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            ;
            System.out.println("Max memory used (gb): " + mem/1000000000D);
        });

        CompletableFuture<Void> allOf = CompletableFuture.allOf(mainProcessFuture, memUsageFuture);
        allOf.get();
    } catch (Exception e) {
        e.printStackTrace();
    }
}
0
mob