web-dev-qa-db-ja.com

JavaプログラムがJavaのランタイムAPIを介して使用するメモリを取得するにはどうすればよいですか?

似たような質問がありますが、この特定の質問に答えるのを避けているようです。 my Java programがJavaのランタイムAPIを介して使用するメモリを取得するにはどうすればよいですか?

答え here は、私がこのようなことができることを示します:

System.out.println("KB: " + (double) (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / 1024);

ただし、どのプログラムを実行しても、これは常に同じ数を返します。たとえば、以下のプログラムでは、マップにいくつの数字を入れても、メモリ使用量は同じままです。

package memoryTest;

import Java.util.HashMap;
import Java.util.Map;

public class MemoryTest {

    static Map<Integer, NewObject> map = new HashMap<Integer, NewObject>();

    public static void main(String[] args){

        System.out.println("KB: " + (double) (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / 1024);
        fillMemory(25);

        System.out.println("KB: " + (double) (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / 1024);
    }

    static int j=0;
    public static void fillMemory(int i){

        for(int k=0; k< 2000; k++)
            map.put(j++, new NewObject());

    }


    public static class NewObject{
        long i = 0L;
        long j = 0L;
        long k = 0L;
    }

}

cambeccのメインメソッドを介した出力は次のとおりです。

3085、合計:128516096、無料:127173744、差分:671120

173579、合計:128516096、無料:110033976、差分:671128

335207、合計:128516096、無料:92417792、差分:637544

672788、合計:224198656、無料:159302960、差分:1221520

1171480、合計:224198656、無料:106939136、差分:1221544

1489771、合計:368377856、無料:227374816、差分:1212984

1998743、合計:368377856、無料:182494408、差分:1212984

29
Matt

あなたはそれを正しくやっています。メモリ使用量を取得する方法は、説明したとおりです。

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

しかし、プログラムが常に同じメモリ使用量を返すのは、freeMemoryメソッドの精度の制限を克服するのに十分なオブジェクトを作成していないためです。バイト解像度がありますが、精度freeMemoryの必要性については保証されていません。 javadocは次のように言っています:

将来割り当てられるオブジェクトに現在使用可能なメモリの総量の概算(バイト単位)。

次を試してください。2つのmillionNewObjectインスタンスが作成され、freeMemoryの結果が変わるたびに出力されます。

public static void main(String[] args) {
    Runtime rt = Runtime.getRuntime();
    long prevTotal = 0;
    long prevFree = rt.freeMemory();

    for (int i = 0; i < 2_000_000; i++) {
        long total = rt.totalMemory();
        long free = rt.freeMemory();
        if (total != prevTotal || free != prevFree) {
            System.out.println(
                String.format("#%s, Total: %s, Free: %s, Diff: %s",
                    i, 
                    total,
                    free,
                    prevFree - free));
            prevTotal = total;
            prevFree = free;
        }
        map.put(i, new NewObject());
    }
}

私のマシンでは、次のような出力が表示されます

#0, Total: 513998848, Free: 508635256, Diff: 0
#21437, Total: 513998848, Free: 505953496, Diff: 2681760
#48905, Total: 513998848, Free: 503271728, Diff: 2681768
#73394, Total: 513998848, Free: 500589960, Diff: 2681768
#103841, Total: 513998848, Free: 497908192, Diff: 2681768
...

21,437番目のオブジェクトがインスタンス化されるまで、報告された空きメモリがどのように変化しなかったかに注目してください。数値は、使用しているJVM(Java7 Win 64ビット)のfreeMemoryが2.5MBをわずかに超える精度を持っていることを示唆しています(ただし、実験を実行すると、この数値は異なります)。

-編集-

このコードは上記と同じですが、メモリ使用量に関する詳細を出力します。 JVMのメモリ使用量の振る舞いが少し明確になっていることを願っています。ループ内で新しいオブジェクトを継続的に割り当てます。各反復中に、totalMemoryまたはfreeMemoryが最後の反復と同じ場合、何も出力しません。ただし、どちらかが変更された場合、現在のメモリ使用量を報告します。 値は、現在の使用状況と以前のメモリレポートの違いを表します。

public static void main(String[] args) {
    Runtime rt = Runtime.getRuntime();
    long prevTotal = 0;
    long prevFree = rt.freeMemory();

    for (int i = 0; i < 2_000_000; i++) {
        long total = rt.totalMemory();
        long free = rt.freeMemory();
        if (total != prevTotal || free != prevFree) {
            long used = total - free;
            long prevUsed = (prevTotal - prevFree);
            System.out.println(
                "#" + i +
                ", Total: " + total +
                ", Used: " + used +
                ", ∆Used: " + (used - prevUsed) +
                ", Free: " + free +
                ", ∆Free: " + (free - prevFree));
            prevTotal = total;
            prevFree = free;
        }
        map.put(i, new NewObject());
    }
}

私のノートブックには、次の出力が表示されます。 OS、ハードウェア、JVM実装などによって結果が異なることに注意してください。

#0, Total: 83427328, Used: 1741048, ∆Used: 83427328, Free: 81686280, ∆Free: 0
#3228, Total: 83427328, Used: 1741080, ∆Used: 32, Free: 81686248, ∆Free: -32
#3229, Total: 83427328, Used: 2176280, ∆Used: 435200, Free: 81251048, ∆Free: -435200
#7777, Total: 83427328, Used: 2176312, ∆Used: 32, Free: 81251016, ∆Free: -32
#7778, Total: 83427328, Used: 2611536, ∆Used: 435224, Free: 80815792, ∆Free: -435224
...
#415056, Total: 83427328, Used: 41517072, ∆Used: 407920, Free: 41910256, ∆Free: -407920
#419680, Total: 145358848, Used: 39477560, ∆Used: -2039512, Free: 105881288, ∆Free: 63971032
#419681, Total: 145358848, Used: 40283832, ∆Used: 806272, Free: 105075016, ∆Free: -806272
...

このデータからいくつかの観察があります:

  1. 使用済みメモリは予想どおり増加する傾向があります。使用済みメモリには、ライブオブジェクトとガベージが含まれます。
  2. ただし、ガベージは破棄されているため、GC中にメモリ減少を使用しました。たとえば、これは#419680で発生しました。
  3. 空きメモリの量は、バイト単位ではなくチャンク単位で減少します。チャンクのサイズはさまざまです。チャンクは32バイトのように非常に小さい場合もありますが、通常は400Kや800Kのように大きくなります。そのため、チャンクサイズはかなり異なるようです。ただし、合計ヒープサイズと比較すると、変動はごくわずかです。たとえば、#419681では、チャンクサイズは合計ヒープサイズのわずか0.6%です。
  4. 予想どおり、GCが起動してガベージをクリーンアップするまで、空きメモリは減少する傾向があります。これが発生すると、廃棄されたガベージの量に応じて、空きメモリがかなり劇的に増加します。
  5. このテストでは大量のゴミが生成されます。ハッシュマップのサイズが大きくなると、ハッシュマップのコンテンツが再ハッシュされ、大量のゴミが生成されます。
55
cambecc

私は次の方法を持っています

public static long getMaxMemory() {
    return Runtime.getRuntime().maxMemory();
}

public static long getUsedMemory() {
    return getMaxMemory() - getFreeMemory();
}

public static long getTotalMemory() {
    return Runtime.getRuntime().totalMemory();
}

public static long getFreeMemory() {
    return Runtime.getRuntime().freeMemory();
}

(使用済み)メモリをバイト単位で返します。

MiBに再計算する場合は、次のようにします。

private static final long MEGABYTE_FACTOR = 1024L * 1024L;
private static final DecimalFormat ROUNDED_DOUBLE_DECIMALFORMAT;
private static final String MIB = "MiB";

static {
    DecimalFormatSymbols otherSymbols = new DecimalFormatSymbols(Locale.ENGLISH);
    otherSymbols.setDecimalSeparator('.');
    otherSymbols.setGroupingSeparator(',');
    ROUNDED_DOUBLE_DECIMALFORMAT = new DecimalFormat("####0.00", otherSymbols);
    ROUNDED_DOUBLE_DECIMALFORMAT.setGroupingUsed(false);
}


    public static String getTotalMemoryInMiB() {
        double totalMiB = bytesToMiB(getTotalMemory());
        return String.format("%s %s", ROUNDED_DOUBLE_DECIMALFORMAT.format(totalMiB), MIB);
    }

    public static String getFreeMemoryInMiB() {
        double freeMiB = bytesToMiB(getFreeMemory());
        return String.format("%s %s", ROUNDED_DOUBLE_DECIMALFORMAT.format(freeMiB), MIB);
    }

    public static String getUsedMemoryInMiB() {
        double usedMiB = bytesToMiB(getUsedMemory());
        return String.format("%s %s", ROUNDED_DOUBLE_DECIMALFORMAT.format(usedMiB), MIB);
    }

    public static String getMaxMemoryInMiB() {
        double maxMiB = bytesToMiB(getMaxMemory());
        return String.format("%s %s", ROUNDED_DOUBLE_DECIMALFORMAT.format(maxMiB), MIB);
    }

    public static double getPercentageUsed() {
        return ((double) getUsedMemory() / getMaxMemory()) * 100;
    }

    public static String getPercentageUsedFormatted() {
        double usedPercentage = getPercentageUsed();
        return ROUNDED_DOUBLE_DECIMALFORMAT.format(usedPercentage) + "%";
    }
3
Spektakulatius