Skip to content

Wie kann ich den Speicher, den mein Java-Programm verwendet, über die Java Runtime api abrufen?

Nachdem wir in verschiedenen Repositories und Foren nachgefragt haben, haben wir endlich die Antwort gefunden, die wir Ihnen unten zeigen.

Lösung:

Sie machen es richtig. Der Weg, den Speicherverbrauch zu ermitteln, ist genau so, wie Sie es beschrieben haben:

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

Der Grund dafür, dass Ihr Programm immer den gleichen Speicherverbrauch zurückgibt, ist, dass Sie nicht genügend Objekte erstellen, um die Genauigkeitseinschränkungen des freeMemory Methode zu überwinden. Obwohl die Methode über Byte Auflösung hat, gibt es keine Garantie dafür, wie genaufreeMemory sein muss. Die Javadoc sagt so viel:

ein Näherungswert für die Gesamtmenge an Speicher, die derzeit für zukünftige zugewiesene Objekte zur Verfügung steht, gemessen in Bytes.

Versuchen Sie das Folgende, das zwei Objekte erzeugt MillionenNewObject Instanzen und gibt jedes Mal das Ergebnis von freeMemory ändert:

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());
    }
}

Auf meinem Rechner sehe ich eine Ausgabe wie die folgende

#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
...

Ist Ihnen aufgefallen, dass sich der gemeldete freie Speicher erst nach der Instanziierung des 21.437sten Objekts geändert hat? Die Zahlen legen nahe freeMemory für die von mir verwendete JVM (Java7 Win 64-Bit) eine Genauigkeit von etwas mehr als 2,5 MB hat (wenn Sie das Experiment durchführen, werden Sie feststellen, dass diese Zahl variiert).

-- Bearbeiten.

Dieser Code ist derselbe wie oben, gibt aber mehr Details zur Speichernutzung aus. Es ist hoffentlich etwas klarer, wie sich die Speichernutzung der JVM verhält. Wir weisen kontinuierlich neue Objekte in einer Schleife zu. Während jeder Iteration, wenn die totalMemory oder freeMemory gleich ist wie bei der letzten Iteration, geben wir nichts aus. Wenn sich jedoch einer der beiden Werte geändert hat, wird der aktuelle Speicherverbrauch gemeldet. Die Werte stellen die Differenz zwischen der aktuellen Nutzung und dem letzten Speicherbericht dar.

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());
    }
}

Auf meinem Notebook sehe ich die folgende Ausgabe. Beachten Sie, dass Ihre Ergebnisse je nach Betriebssystem, Hardware, JVM-Implementierung usw. unterschiedlich sein werden:

#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
...

Aus diesen Daten ergeben sich einige Beobachtungen:

  1. Der verwendete Speicher nimmt erwartungsgemäß zu. Der benutzte Speicher umfasst Live-Objekte und Garbage.
  2. Aber der verwendete Speicher nimmt ab während einer GC, weil Garbage entsorgt wurde. Dies ist zum Beispiel bei #419680 aufgetreten.
  3. Die Menge an freiem Speicher wird in Chunks reduziert, nicht Byte für Byte. Die Chunks sind unterschiedlich groß. Manchmal sind die Chunks wirklich winzig, wie 32 Byte, aber normalerweise sind sie größer, wie 400K oder 800K. Es scheint also, dass die Größe der Chunks sehr unterschiedlich ist. Verglichen mit der Gesamtgröße des Heaps erscheinen die Abweichungen jedoch winzig. Bei #419681 zum Beispiel beträgt die Chunk-Größe nur 0,6 % der gesamten Heap-Größe.
  4. Der freie Speicher nimmt erwartungsgemäß ab, bis ein GC einsetzt und den Müll aufräumt. Wenn dies geschieht, steigt der freie Speicher ziemlich dramatisch an, abhängig von der Menge des verworfenen Mülls.
  5. Dieser Test erzeugt eine Menge Müll. Da die Hashmap immer größer wird, wird ihr Inhalt neu aufbereitet, wodurch eine Menge Müll erzeugt wird.

Ich habe die folgenden Methoden

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();
}

die den (verwendeten) Speicher in Bytes zurückgeben.

Wenn du auf MiB umrechnen willst, habe ich:

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) + "%";
    }

Hier können Sie die Kommentare und Bewertungen der Benutzer sehen

Denken Sie daran, diese Chronik zu verbreiten, wenn sie Ihr Problem gelöst hat.



Nutzen Sie unsere Suchmaschine

Suche
Generic filters

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.