いくつかのコレクションを操作して、重い処理を行うクラスがあると仮定します。私がやりたいのは、そのような操作がメモリ不足にならないようにすることであり、さらに良いことに、使用可能なメモリ量のしきい値を設定することです。
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?
}
}
これを行うための正しいアプローチは何ですか?または、これは不可能/実行不可能ですか?
私はいくつかのオプションを考えることができます:
メモリをカウントする独自のベンチマークテストを作成することもできます。アイデアは
System.gc()
、memoryBefore = runtime.totalMemory() - runtime.freeMemory()
System.gc()
、memoryAfter = runtime.totalMemory() - runtime.freeMemory()
これは、 軽量マイクロベンチマークツール で使用した手法で、バイト精度でメモリ割り当てを測定できます。
プロファイラ(例: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));
現在のメモリ使用量を測定するには:
Runtime.getRuntime().freeMemory()
、Runtime.getRuntime().totalMemory()
次に例を示します。 OSレベルのシステム情報を取得
しかし、この測定は正確ではありませんが、多くの情報を提供できます。もう1つの問題は、予測不能なGC
にあります。
同様のことを行うNettyの例を次に示します。 MemoryAwareThreadPoolExecutor 。グアバの キャッシュクラス には、サイズベースのエビクションもあります。これらのソースを見て、彼らがしていることをコピーすることができます。特に、Nettyの仕組みは次のとおりです オブジェクトサイズの推定 。基本的に、メソッドで生成するオブジェクトのサイズを推定し、カウントを保持します。
全体的なメモリ情報(使用可能なヒープ/使用量など)を取得すると、メソッドに割り当てるメモリ使用量を決定するのに役立ちますが、個々のメソッド呼び出しで使用されたメモリ量を追跡することはできません。
そうは言っても、これが正当に必要になることは非常にまれです。ほとんどの場合、特定のポイントに存在できるオブジェクトの数を制限することで(たとえば、制限されたキューを使用することで)メモリ使用量を制限するだけで十分であり、実装ははるかに簡単です。
この質問は、Javaが処理中に多くの短命オブジェクトを割り当てることができ、後でガベージコレクション中に収集される方法のため、少し注意が必要です。受け入れられた答えでは、複数のSystem.gc()
呼び出しでループ構造を導入しても、メソッド呼び出しの間にガベージコレクションが実行される可能性があります。
より良い方法は、代わりに https://cruftex.net/2017/03/28/The-6-Memory-Metrics-You-Should-Track-in-Your- Java-Benchmarks.html 、System.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);
}
これは、特定の時間にコードによって実際に割り当てられたメモリの概算のみを提供しますが、値は通常、通常関心のあるものにはるかに近くなります。
これは、別のスレッドでメモリ使用量を実行するサンプルコードです。 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();
}
}