web-dev-qa-db-fra.com

Quelles sont les meilleures pratiques de gestion de la mémoire Java?

Je reprends certaines applications d'un développeur précédent. Lorsque j'exécute les applications via Eclipse, l'utilisation de la mémoire et la taille de la mémoire mémoire augmentent considérablement. Après une enquête plus poussée, je constate qu’ils créaient un objet qui se superposait en boucle ainsi que d’autres choses.

J'ai commencé à faire du nettoyage. Mais plus je traversais, plus je me posais de questions du genre "cela fera-t-il réellement quelque chose?"

Par exemple, au lieu de déclarer une variable en dehors de la boucle mentionnée ci-dessus et de définir sa valeur dans la boucle ... ils ont créé l'objet dans la boucle. Ce que je veux dire est:

for(int i=0; i < arrayOfStuff.size(); i++) {
    String something = (String) arrayOfStuff.get(i);
    ...
}

versus

String something = null;
for(int i=0; i < arrayOfStuff.size(); i++) {
    something = (String) arrayOfStuff.get(i);
}

Ai-je tort de dire que la boucle du bas est meilleure? Peut-être que je me trompe. 

Aussi, qu'en est-il après la deuxième boucle ci-dessus, je remets à zéro "quelque chose"? Cela effacerait-il de la mémoire?

Dans les deux cas, quelles sont les bonnes pratiques de gestion de la mémoire que je pourrais suivre et qui contribueront à limiter l'utilisation de la mémoire dans mes applications?

Mettre à jour:

J'apprécie les commentaires de tous jusqu'à présent. Cependant, je ne posais pas vraiment de questions sur les boucles ci-dessus (même si, selon votre conseil, je suis retourné à la première boucle). J'essaie de mettre en place des pratiques exemplaires que je peux surveiller. Quelque chose dans les lignes de "quand vous avez fini d'utiliser une collection, effacez-le". Je dois vraiment m'assurer que ces applications n'utilisent pas autant de mémoire.

37
Ascalonian

N'essayez pas de déjouer la VM. La première boucle est la meilleure pratique suggérée, à la fois pour la performance et la maintenabilité. Redéfinir la référence sur null après la boucle ne garantit pas une libération immédiate de la mémoire. Le GC fera de son mieux si vous utilisez la portée minimale possible.

Les ouvrages qui couvrent ces éléments en détail (du point de vue de l'utilisateur) sont Effective Java 2 et Modèles d'implémentation .

Si vous souhaitez en savoir plus sur les performances et les éléments internes de VM, vous devez voir des conférences ou lire des livres de Brian Goetz .

37
cherouvim

Ces deux boucles sont équivalentes sauf pour la portée de something; voir cette question pour plus de détails.

Bonnes pratiques générales? Euh, voyons: ne stockez pas de grandes quantités de données dans des variables statiques à moins d'avoir une bonne raison Supprimez les objets volumineux des collections lorsque vous en avez terminé. Et oh oui, "Mesurez, ne devinez pas." Utilisez un profileur pour voir où la mémoire est allouée.

9
Michael Myers

Aucun objet n'a été créé dans vos deux exemples de code. Vous définissez simplement une référence d'objet à une chaîne qui se trouve déjà dans arrayOfStuff. Donc, au niveau de la mémoire, il n'y a pas de différence.

8
Kees de Kooter

La JVM est la meilleure pour libérer des objets éphémères. Essayez de ne pas allouer d'objets dont vous n'avez pas besoin. Toutefois, vous ne pouvez pas optimiser l'utilisation de la mémoire tant que vous ne comprenez pas votre charge de travail, la durée de vie de l'objet et ses tailles. Un profileur peut vous dire ceci.

Enfin, la première chose à éviter est de ne jamais utiliser les finaliseurs. Les finaliseurs interfèrent avec la récupération de place, car l'objet ne peut pas être simplement libéré, mais doit être mis en file d'attente pour la finalisation, ce qui peut ou non se produire. Il est préférable de ne jamais utiliser de finaliseurs.

En ce qui concerne l'utilisation de la mémoire que vous voyez dans Eclipse, ce n'est pas nécessairement pertinent. Le GC fera son travail en fonction de la quantité de mémoire disponible. Si vous avez beaucoup de mémoire libre, vous ne verrez peut-être pas un seul GC avant la fermeture de l'application. Si votre application manque de mémoire, seul un véritable profileur peut vous indiquer où se trouvent les fuites ou les inefficacités.

Les deux boucles utiliseront essentiellement la même quantité de mémoire, toute différence serait négligeable. "String quelque chose" crée uniquement une référence à un objet, pas un nouvel objet en lui-même et donc toute mémoire supplémentaire utilisée est petite. De plus, le compilateur/associé à la machine virtuelle Java optimisera de toute façon le code généré. 

Pour les pratiques de gestion de la mémoire, vous devriez vraiment essayer de profiler votre mémoire pour mieux comprendre où se trouvent les goulots d'étranglement. Recherchez en particulier les références statiques pointant vers un gros bloc de mémoire, car elles ne seront jamais collectées. 

Vous pouvez également consulter Références faibles et autres classes spécialisées de gestion de la mémoire. 

Enfin, gardez à l'esprit que si une application utilise de la mémoire, il pourrait y avoir une raison à cela ....

Mise à jour La clé de la gestion de la mémoire réside dans les structures de données, ainsi que dans quelle quantité de performances vous avez besoin/quand. Le compromis est souvent entre les cycles de la mémoire et du processeur. 

Par exemple, la mise en cache peut occuper une grande quantité de mémoire, ce qui permet notamment d'améliorer les performances, car vous essayez d'éviter une opération coûteuse. 

Réfléchissez donc à vos structures de données et veillez à ne pas conserver les éléments en mémoire plus longtemps que nécessaire. S'il s'agit d'une application Web, évitez de stocker beaucoup de données dans la variable de session, évitez les références statiques à d'énormes réserves de mémoire, etc. 

5
Jean Barmash

La première boucle est meilleure. Parce que

  • la variable quelque chose sera clair plus vite (théorique)
  • le programme est préférable de lire.

Mais, du point de vue de la mémoire, cela n’est pas pertinent.

Si vous avez des problèmes de mémoire, vous devez indiquer le lieu où il est consommé.

4
Horcrux7

À mon avis, vous devriez éviter les micro-optimisations comme celles-ci. Ils coûtent beaucoup de cycles cérébraux, mais la plupart du temps ont peu d'impact.

Votre application a probablement quelques structures de données centrales. Ce sont ceux qui devraient vous inquiéter. Par exemple, si vous les remplissez, préallouez-les avec une bonne estimation de la taille afin d’éviter les redimensionnements répétés de la structure sous-jacente. Ceci s'applique particulièrement à StringBuffer, ArrayList, HashMap et autres. Concevez bien votre accès à ces structures, de sorte que vous n’ayez pas à copier beaucoup.

Utilisez les algorithmes appropriés pour accéder aux structures de données. Au niveau le plus bas, comme la boucle que vous avez mentionnée, utilisez Iterators, ou du moins évitez d'appeler .size() tout le temps. (Oui, vous demandez chaque fois la taille de la liste, ce qui la plupart du temps ne change pas.) Au fait, j'ai souvent vu une erreur similaire avec Maps. Les gens parcourent chaque valeur avec keySet() et get, au lieu de se contenter de la première fois avec entrySet(). Le gestionnaire de mémoire vous remerciera pour les cycles de processeur supplémentaires.

4
Ronald Blaschke

Comme l'a suggéré un poster ci-dessus, utilisez profiler pour mesurer l'utilisation de la mémoire (et/ou du processeur) de certaines parties de votre programme plutôt que d'essayer de le deviner. Vous pourriez être surpris de ce que vous trouvez!

Il y a aussi un avantage supplémentaire à cela. Vous comprendrez davantage votre langage de programmation et votre application.

J'utilise VisualVM pour le profilage et le recommande fortement. Il vient avec la distribution jdk/jre.

2
riz

Eh bien, la première boucle est meilleure, car la portée de quelque chose est plus petite. En ce qui concerne la gestion de la mémoire, cela ne fait pas une grande différence.

La plupart des problèmes de mémoire Java surviennent lorsque vous stockez des objets dans une collection, sans oublier de les supprimer. Sinon, le GC fait son travail assez bien.

1
siddhadev

Le premier exemple est bien. Il n’ya pas d’allocation de mémoire, à part une allocation de variable de pile et une désallocation à chaque fois dans la boucle (très économique et rapide). 

La raison en est que tout ce qui est "alloué" est une référence, qui est une variable de pile sur 4 octets (de toute façon sur la plupart des systèmes 32 bits). Une variable de pile est "allouée" en ajoutant à une adresse de mémoire représentant le haut de la pile, et est donc très rapide et bon marché.

Il faut faire attention aux boucles telles que:

for (int i = 0; i < some_large_num; i++)
{
   String something = new String();
   //do stuff with something
}

comme cela est en train de faire la mémoire allocatiton.

1
workmad3

Si vous ne l'avez pas déjà fait, je vous suggère d'installer la plate-forme Eclipse Test & Performance Tools (TPTP). Si vous souhaitez vider et inspecter le segment de mémoire, consultez SDK jmap et jhat tools. Voir également Surveillance et gestion des applications de la plate-forme Java SE 6 .

1
McDowell

"Ai-je tort de dire que la boucle du bas est meilleure?", La réponse est NON, non seulement c'est mieux, mais dans le même cas, il est nécessaire ... La définition de la variable (pas le contenu), est créée dans la mémoire limité, dans le premier exemple, chaque boucle crée une instance dans cette mémoire, et si la taille de "arrayOfStuff" est grande, une "Erreur de mémoire insuffisante: espace de segment de mémoire Java" peut se produire ....

0
Eduard

D'après ce que je comprends, la boucle du bas n'est pas meilleure. La raison en est que même si vous essayez de réutiliser une seule référence (Ex-quelque chose), le fait est que l'objet (Ex - arrayOfStuff.get (i)) est toujours référencé à partir de la liste (arrayOfStuff). Pour que les objets puissent être collectionnés, ils ne doivent être référencés à aucun endroit. Si vous êtes sûr de la durée de vie de la liste après ce point, vous pouvez décider de supprimer/libérer les objets de celle-ci, dans une boucle distincte. 

L’optimisation pouvant être effectuée à partir d’une perspective statique (c’est-à-dire qu’aucune modification n’est apportée à cette liste à partir d’un autre thread), il est préférable d’éviter de manière répétée l’appel de size (). C’est-à-dire si vous ne vous attendez pas à ce que la taille change, alors pourquoi la calculer encore et encore; Après tout, ce n'est pas un tableau.length, c'est list.size ().

0
Narita