web-dev-qa-db-fra.com

Comment calculer l'utilisation de la mémoire HashMap en Java?

Au cours d’une interview, on m’a demandé de calculer l’utilisation de la mémoire pour HashMap et la quantité de mémoire estimée qu’elle consommerait si vous aviez 2 millions d’éléments.

Par exemple:

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

La cartographie est comme ça. Une clé en tant que chaîne et un tableau de chaînes en tant que clé.

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

Comment estimerais-je l'utilisation de la mémoire de cet objet HashMap en Java?

19
insomiac

La réponse courte

Pour connaître la taille d'un objet, j'utiliserais un profileur. Dans YourKit, par exemple, vous pouvez rechercher l'objet puis le faire calculer sa taille profonde. Cela vous donnera une idée juste de la quantité de mémoire qui serait utilisée si l’objet était autonome et si sa taille était conservatrice.

Les galettes

Si des parties de l'objet sont réutilisées dans d'autres structures, par ex. Littéraux de chaîne, vous ne libérerez pas autant de mémoire en la supprimant. En fait, supprimer une référence à HashMap pourrait ne libérer aucune mémoire.

Qu'en est-il de la sérialisation?

La sérialisation de l'objet est une approche pour obtenir une estimation, mais elle peut être très différente, car la surcharge de la sérialisation et le codage sont différents en mémoire et en flux d'octets. La quantité de mémoire utilisée dépend de la machine virtuelle Java (et de l'utilisation éventuelle de références 32/64 bits), mais le format de la sérialisation est toujours le même.

par exemple.

Dans la machine virtuelle Java de Sun/Oracle, un entier peut prendre 16 octets pour l'en-tête, 4 octets pour le nombre et 4 octets de remplissage (les objets sont alignés sur 8 octets en mémoire), soit 24 octets au total. Toutefois, si vous sérialisez un entier, il faut 81 octets, sérialisez deux entiers et ils prennent 91 octets. c'est-à-dire que la taille du premier entier est gonflée et que le second est inférieur à ce qui est utilisé en mémoire.

String est un exemple beaucoup plus complexe. Dans la JVM Sun/Oracle, il contient 3 valeurs int et une référence char[]. Donc, vous pouvez supposer qu'il utilise un en-tête de 16 octets plus 3 * 4 octets pour le ints, 4 octets pour le char[], 16 octets pour la surcharge du char[], puis deux octets par caractère, alignés sur une limite de 8 octets ...

Quels drapeaux peuvent changer la taille?

Si vous avez des références 64 bits, la référence char[] est longue de 8 octets, ce qui donne 4 octets de remplissage. Si vous avez une machine virtuelle Java 64 bits, vous pouvez utiliser +XX:+UseCompressedOops pour utiliser des références 32 bits. (Regardez donc la taille en bits de la machine virtuelle Java ne vous dit pas à elle seule la taille de ses références)

Si vous avez -XX:+UseCompressedStrings, la machine virtuelle Java utilisera un octet [] au lieu d'un tableau de caractères lorsqu'il le pourra. Cela peut légèrement ralentir votre application, mais pourrait considérablement améliorer votre consommation de mémoire. Lorsqu'un octet [] est utilisé, la mémoire utilisée est de 1 octet par caractère. ;) Remarque: pour une chaîne de 4 caractères, comme dans l'exemple, la taille utilisée est la même en raison de la limite de 8 octets. 

Qu'entendez-vous par "taille"?

Comme il a été souligné, HashMap and List est plus complexe car de nombreuses chaînes, voire toutes, peuvent être réutilisées, voire même des littéraux. Ce que vous entendez par "taille" dépend de la manière dont il est utilisé. c'est-à-dire combien de mémoire la structure utiliserait-elle seule? Combien serait libéré si la structure était mise au rebut? Quelle quantité de mémoire serait utilisée si vous copiiez la structure? Ces questions peuvent avoir des réponses différentes.

Que pouvez-vous faire sans profileur?

Si vous pouvez déterminer que la taille conservatrice probable est suffisamment petite, la taille exacte importe peu. Le cas conservateur est susceptible de créer chaque chaîne et chaque entrée à partir de zéro. (Je dis seulement que vraisemblablement, une HashMap peut avoir une capacité de 1 milliard d'entrées même si elle est vide. Les chaînes avec un seul caractère peuvent être une sous-chaîne d'une chaîne avec 2 milliards de caractères)

Vous pouvez effectuer System.gc (), utiliser la mémoire libre, créer les objets, effectuer une autre opération System.gc () et voir la réduction de la quantité de mémoire disponible. Vous devrez peut-être créer l'objet plusieurs fois et prendre une moyenne. Répétez cet exercice plusieurs fois, mais cela peut vous donner une idée juste.

(BTW Bien que System.gc () ne soit qu'un indice, la JVM Sun/Oracle effectuera un GC complet à chaque fois par défaut)

18
Peter Lawrey

Je pense que la question devrait être clarifiée car il y a une différence entre la taille de HashMap et la taille de HashMap + les objets contenus dans HashMap.

Si vous tenez compte de la taille de HashMap, dans l'exemple que vous avez fourni, HashMap stocke une référence à la chaîne "aby" et une référence à la liste. Les multiples éléments de la liste sont donc sans importance. Seule la référence à la liste est stockée dans la valeur.

Dans une JVM 32 bits, dans une entrée Map, vous avez 4 octets pour la référence "aby" + 4 octets pour la référence de liste + 4 octets pour la propriété int "hashcode" de l'entrée Map + 4 octets pour la propriété "next" de l'entrée de la carte. 

Vous ajoutez également les références à 4 * (X-1) octets, où "X" correspond au nombre de compartiments vides créés par HashMap lorsque vous avez appelé le constructeur new HashMap<String,List<String>>() . Selon http://docs.Oracle.com/javase/6/docs/api/Java/util/HashMap.html , il devrait être 16.

Il existe également loadFactor, modCount, seuil et taille qui sont tous de type int primitif (16 octets supplémentaires) et en-tête (8 octets).

Donc au final, la taille de votre HashMap ci-dessus serait de 4 + 4 + 1 + (4 * 15) + 16 + 8 = 93 octets

Il s'agit d'une approximation basée sur des données appartenant à HashMap. Je pense que l'intervieweur voulait peut-être savoir si vous connaissiez le fonctionnement de HashMap (par exemple, le constructeur par défaut crée un tableau de 16 compartiments pour l'entrée Map, le fait que la taille des objets stockés dans HashMap n'affecte pas la taille de HashMap car il ne stocke que les références).

HashMap est si largement utilisé que, dans certaines circonstances, il convient d’utiliser les constructeurs avec une capacité initiale et un facteur de charge.

1
J.M. Kenny

vous ne pouvez pas savoir à l'avance sans savoir ce que sont toutes les chaînes, combien d'éléments sont dans chaque liste, ou sans savoir si les chaînes sont toutes des références uniques.

Le seul moyen de savoir avec certitude est de sérialiser l'ensemble dans un tableau d'octets (ou un fichier temporaire) et de voir exactement combien d'octets il s'agissait.

0
John Gardner