wake-up-neo.com

Wie berechnet man die HashMap-Speichernutzung in Java?

In einem Interview wurde ich gebeten, die Speichernutzung für HashMap und den geschätzten Speicherbedarf bei 2 Millionen Elementen zu berechnen.

Zum Beispiel:

Map <String,List<String>> mp=new HashMap <String,List<String>>();

Das Mapping ist so. Ein Schlüssel als Zeichenfolge und ein Array von Zeichenfolgen als Schlüssel.

key   value
----- ---------------------------
abc   ['hello','how']
abz   ['hello','how','are','you']

Wie würde ich die Speichernutzung dieses HashMap-Objekts in Java einschätzen?

19
insomiac

Die kurze Antwort

Um herauszufinden, wie groß ein Objekt ist, würde ich einen Profiler verwenden. In YourKit können Sie beispielsweise nach dem Objekt suchen und dann die Tiefengröße berechnen lassen. Auf diese Weise erhalten Sie eine gute Vorstellung davon, wie viel Speicher verwendet werden würde, wenn das Objekt alleine wäre und eine konservative Größe für das Objekt hat.

Die Probleme

Wenn Teile des Objekts in anderen Strukturen wiederverwendet werden, z. String-Literale, Sie werden nicht so viel Speicher freigeben, indem Sie sie löschen. Beim Löschen eines Verweises auf die HashMap wird tatsächlich überhaupt kein Speicherplatz freigegeben.

Was ist mit der Serialisierung?

Die Serialisierung des Objekts ist ein Ansatz, um eine Schätzung zu erhalten. Dies kann jedoch ausbleiben, da der Serialisierungs-Overhead und die Codierung sich im Speicher und in einem Byte-Stream unterscheiden. Wie viel Speicher verwendet wird, hängt von der JVM ab (und davon, ob sie 32/64-Bit-Referenzen verwendet), das Serialisierungsformat ist jedoch immer dasselbe.

z.B.

In der JVM von Sun/Oracle kann ein Integer-Wert 16 Byte für den Header, 4 Byte für die Anzahl und 4 Byte-Auffüllung (die Objekte sind 8-Byte-Speicher im Speicher) enthalten, insgesamt 24 Byte. Wenn Sie jedoch eine ganze Zahl serialisieren, werden 81 Byte benötigt, zwei ganze Zahlen werden serialisiert und 91 Byte. die Größe der ersten Ganzzahl ist aufgeblasen und die zweite Ganzzahl ist kleiner als die im Speicher verwendete.

String ist ein viel komplexeres Beispiel. In der Sun/Oracle-JVM enthält es 3 int-Werte und eine char[]-Referenz. Sie können also davon ausgehen, dass der Header 16 Byte plus 3 * 4 Byte für die Variable ints, 4 Byte für den Code char[], 16 Byte für den Overhead des Codes char[] und zwei Bytes pro Zeichen, die an einer 8-Byte-Grenze ausgerichtet sind, verwendet werden.

Welche Flags können die Größe ändern?

Wenn Sie über 64-Bit-Referenzen verfügen, ist die char[]-Referenz 8 Byte lang, was zu einer Auffüllung von 4 Byte führt. Wenn Sie über eine 64-Bit-JVM verfügen, können Sie +XX:+UseCompressedOops verwenden, um 32-Bit-Referenzen zu verwenden. (Betrachten Sie die JVM-Bitgröße allein nicht, um die Größe der Referenzen anzugeben.)

Wenn Sie -XX:+UseCompressedStrings verwenden, verwendet die JVM ein Byte [] anstelle eines Char-Arrays, wenn dies möglich ist. Dies kann die Anwendung etwas verlangsamen, kann jedoch den Speicherverbrauch erheblich verbessern. Wenn ein Byte [] verwendet wird, beträgt der Speicherplatz 1 Byte pro Zeichen. ;) Hinweis: Bei einem String mit 4 Zeichen ist die verwendete Größe aufgrund der 8-Byte-Grenze wie im Beispiel gleich. 

Was meinst du mit "Größe"?

Wie bereits erwähnt, ist 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 es verwendet wird. Wie viel Speicher würde die Struktur alleine beanspruchen? Wie viel würde befreit, wenn die Struktur verworfen würde? Wie viel Speicher würde benötigt, wenn Sie die Struktur kopieren? Diese Fragen können unterschiedliche Antworten haben.

Was kannst du ohne einen Profiler machen?

Wenn Sie feststellen können, dass die wahrscheinliche konservative Größe klein genug ist, spielt die exakte Größe keine Rolle. Der konservative Fall ist wahrscheinlich, wenn Sie jeden String und Eintrag von Grund auf neu erstellen. (Ich sage nur wahrscheinlich, da eine HashMap eine Kapazität von 1 Milliarde Einträgen haben kann, obwohl sie leer ist. Zeichenfolgen mit einem einzelnen Zeichen können eine Unterzeichenfolge einer Zeichenfolge mit 2 Milliarden Zeichen sein.)

Sie können ein System.gc () ausführen, den freien Speicher verwenden, die Objekte erstellen, ein anderes System.gc () ausführen und sehen, wie viel freier Speicherplatz vorhanden ist. Möglicherweise müssen Sie das Objekt viele Male erstellen und einen Durchschnitt ermitteln. Wiederholen Sie diese Übung viele Male, aber Sie können eine gute Vorstellung davon bekommen.

(Übrigens: Während System.gc () nur ein Hinweis ist, führt die Sun/Oracle-JVM standardmäßig jedes Mal eine vollständige GC aus.)

18
Peter Lawrey

Ich denke, dass die Frage geklärt werden sollte, da es einen Unterschied zwischen der Größe der HashMap und der Größe der HashMap + der in der HashMap enthaltenen Objekte gibt.

Wenn Sie die Größe der HashMap berücksichtigen, speichert die HashMap in dem von Ihnen bereitgestellten Beispiel einen Verweis auf den String "aby" und einen Verweis auf die Liste. Die mehrfachen Elemente in der Liste spielen also keine Rolle. Nur der Verweis auf die Liste wird im Wert gespeichert.

In einer 32-Bit-JVM haben Sie in einem Map-Eintrag 4 Bytes für die Referenz "aby" + 4 Bytes für die Listenreferenz + 4 Bytes für die Eigenschaft "Hashcode" int des Map-Eintrags + 4 Bytes für die Eigenschaft "next" des Karteneintrags. 

Sie fügen auch die 4 * (X-1) Bytes-Referenzen hinzu, wobei "X" die Anzahl leerer Buckets ist, die die HashMap erstellt hat, als Sie den Konstruktor new HashMap<String,List<String>>() Aufgerufen haben. Laut http://docs.Oracle.com/javase/6/docs/api/Java/util/HashMap.html sollte es 16 sein.

Es gibt auch loadFactor, modCount, Schwellwert und Größe, die alle vom Typ int (16 weitere Bytes) und Header (8 Bytes) sind.

Am Ende wäre die Größe Ihrer obigen HashMap 4 + 4 + 1 + (4 * 15) + 16 + 8 = 93 Bytes

Dies ist eine Annäherung, die auf Daten basiert, die der HashMap gehören. Ich denke, dass der Interviewer vielleicht daran interessiert war zu sehen, ob Sie wissen, wie HashMap funktioniert (die Tatsache, dass der Standardkonstruktor ein Array mit 16 Buckets für den Map-Eintrag erstellt), und die Größe der Objekte in der HashMap Die HashMap-Größe wird nicht beeinflusst, da nur die Referenzen gespeichert werden.

HashMap wird so häufig verwendet, dass es unter bestimmten Umständen sinnvoll sein sollte, Konstruktoren mit anfänglicher Kapazität und Lastfaktor zu verwenden.

1
J.M. Kenny

sie können nicht im Voraus wissen, ohne zu wissen, was alle Zeichenfolgen sind und wie viele Elemente sich in jeder Liste befinden, oder ohne zu wissen, ob die Zeichenfolgen eindeutige Verweise sind.

Der einzige Weg, um sicher zu sein, besteht darin, das Ganze zu einem Byte-Array (oder einer temporären Datei) zu serialisieren und genau zu sehen, wie viele Bytes das waren.

0
John Gardner