Die kurze Antwort
Um herauszufinden, wie groß ein Objekt ist, würde ich einen Profiler verwenden. In YourKit können Sie zum Beispiel nach dem Objekt suchen und es dann dazu bringen, seine Größe zu berechnen. So erhalten Sie eine ungefähre Vorstellung davon, wie viel Speicherplatz verbraucht würde, wenn das Objekt eigenständig wäre, und dies ist eine konservative Größe für das Objekt.
Die Spitzfindigkeiten
Wenn Teile des Objekts in anderen Strukturen wiederverwendet werden, z. B. in String-Literalen, wird nicht so viel Speicherplatz frei, wenn man es verwirft. Das Verwerfen eines Verweises auf die HashMap könnte sogar überhaupt keinen Speicher freigeben.
Was ist mit der Serialisierung?
Die Serialisierung des Objekts ist ein Ansatz, um eine Schätzung zu erhalten, aber sie kann sehr ungenau sein, da der Serialisierungs-Overhead und die Kodierung im Speicher und in einem Byte-Stream unterschiedlich sind. Wie viel Speicher verwendet wird, hängt von der JVM ab (und davon, ob sie 32/64-Bit-Referenzen verwendet), aber das Serialisierungsformat ist immer dasselbe.
z.B..
In der JVM von Sun/Oracle kann ein Integer 16 Bytes für den Header, 4 Bytes für die Zahl und 4 Bytes Padding (die Objekte sind im Speicher 8-Byte-ausgerichtet), insgesamt 24 Bytes, benötigen. Wenn Sie jedoch einen Integer serialisieren, benötigt er 81 Bytes, serialisieren Sie zwei Integer, benötigen sie 91 Bytes, d.h. die Größe des ersten Integers ist aufgebläht und der zweite Integer ist kleiner als der Speicherplatz.
String ist ein viel komplexeres Beispiel. In der Sun/Oracle JVM enthält er 3 int
Werte und eine char[]
Hinweis. Sie können also davon ausgehen, dass es 16 Byte Header plus 3 * 4 Byte für die int
s, 4 Bytes für die char[]
, 16 Bytes für den Overhead der char[]
und dann zwei Bytes pro Zeichen, ausgerichtet an der 8-Byte-Grenze...
Welche Flaggen können die Größe verändern?
Wenn Sie 64-Bit-Referenzen haben, wird die char[]
Referenz ist 8 Byte lang, was 4 Byte Auffüllung bedeutet. Wenn Sie eine 64-Bit-JVM haben, können Sie +XX:+UseCompressedOops
um 32-Bit-Referenzen zu verwenden. (Ein Blick auf die Bitgröße der JVM allein sagt also nichts über die Größe der Referenzen aus)
Wenn Sie eine -XX:+UseCompressedStrings
verwendet die JVM ein Byte[] anstelle eines Char-Arrays, wenn sie kann. Dies kann Ihre Anwendung etwas verlangsamen, aber den Speicherverbrauch drastisch verbessern. Wenn ein byte[] verwendet wird, beträgt der Speicherverbrauch 1 Byte pro Zeichen ;) Hinweis: Bei einem 4-Zeichen-String, wie im Beispiel, ist die verwendete Größe aufgrund der 8-Byte-Grenze gleich.
Was meinen Sie mit "Größe"?
Wie bereits erwähnt, sind HashMap und List komplexer, da viele, wenn nicht alle, Strings wiederverwendet werden können, möglicherweise String-Literale. Was Sie mit "Größe" meinen, hängt davon ab, wie sie verwendet wird, d. h. wie viel Speicher würde die Struktur allein verbrauchen? Wie viel würde frei werden, wenn die Struktur verworfen würde? Wie viel Speicher wird benötigt, wenn Sie die Struktur kopieren? Auf diese Fragen kann es unterschiedliche Antworten geben.
Was können Sie ohne einen Profiler tun?
Wenn Sie feststellen können, dass die wahrscheinliche konservative Größe klein genug ist, spielt die genaue Größe keine Rolle. Im konservativen Fall müssen Sie wahrscheinlich jede Zeichenfolge und jeden Eintrag von Grund auf neu konstruieren. (Ich sage nur wahrscheinlich, da eine HashMap 1 Milliarde Einträge fassen kann, obwohl sie leer ist. Strings mit einem einzigen Zeichen können ein Sub-String eines Strings mit 2 Milliarden Zeichen sein)
Sie können ein System.gc() ausführen, den freien Speicher entnehmen, die Objekte erstellen, ein weiteres System.gc() ausführen und sehen, um wie viel sich der freie Speicher verringert hat. Möglicherweise müssen Sie das Objekt viele Male erstellen und einen Durchschnitt ermitteln. Wiederholen Sie diese Übung viele Male, aber sie kann Ihnen einen guten Eindruck vermitteln.
(BTW Während System.gc() nur ein Hinweis ist, führt die Sun/Oracle JVM standardmäßig jedes Mal eine vollständige GC durch)