web-dev-qa-db-fra.com

Faut-il éviter la création d'objets en Java?

Un collègue m'a dit qu'en Java la création d'objets est l'opération la plus coûteuse que vous puissiez effectuer. Je ne peux donc conclure que pour créer le moins d'objets possible.

Cela semble quelque peu contrecarrer le but de la programmation orientée objet. Si nous ne créons pas d'objets, nous écrivons simplement un style long de classe C, pour l'optimisation?

253
Slamice

Votre collègue ne sait pas de quoi ils parlent.

Votre opération la plus chère serait de les écouter. Ils ont perdu votre temps à vous orienter de manière erronée vers des informations qui sont obsolètes depuis plus d'une décennie (à la date d'origine, cette réponse a été publiée) ainsi que de devoir passer du temps à publier ici et à faire des recherches Internet pour la vérité.

Espérons qu'ils régurgitent par ignorance quelque chose qu'ils ont entendu ou lu il y a plus de dix ans et qu'ils ne savent pas mieux. Je prendrais aussi tout ce qu'ils disent comme suspect, cela devrait être une erreur bien connue de quiconque se tient au courant de toute façon.

Tout est un objet (sauf primitives)

Tout autre que les primitives (int, long, double, etc.) sont des objets en Java. Il n'y a aucun moyen d'éviter la création d'objets en Java.

La création d'objets dans Java en raison de ses stratégies d'allocation de mémoire est plus rapide que C++ dans la plupart des cas et à toutes fins pratiques par rapport à tout le reste dans la JVM peut être considérée " gratuit " .

Au début comme à la fin des années 1990, au début des années 2000, les implémentations JVM avaient un certain surcoût de performance dans l'allocation réelle des objets. Ce n'est plus le cas depuis au moins 2005.

Si vous syntonisez -Xms pour prendre en charge toute la mémoire dont vous avez besoin pour que votre application s'exécute correctement, le GC peut ne jamais avoir à exécuter et balayer la plupart des ordures dans les implémentations GC modernes, les programmes de courte durée peuvent ne jamais GC du tout.

Il n'essaie pas de maximiser l'espace libre, qui est de toute façon un hareng rouge, il maximise les performances de l'exécution. Si cela signifie que le tas JVM est alloué à presque 100% tout le temps, qu'il en soit ainsi. La mémoire de tas JVM gratuite ne vous donne rien de toute façon.

Il y a une idée fausse selon laquelle le GC va libérer de la mémoire pour le reste du système d'une manière utile, c'est complètement faux!

Le tas JVM ne grandit pas et ne rétrécit pas, de sorte que le reste du système est positivement affecté par la mémoire libre dans le tas JVM . -Xms alloue TOUT ce qui est spécifié au démarrage et son heuristique est de ne jamais vraiment libérer une partie de cette mémoire sur le système d'exploitation pour être partagée avec d'autres processus du système d'exploitation jusqu'à ce que cette instance de la JVM se ferme complètement. -Xms=1GB -Xmx=1GB alloue 1 Go de RAM quel que soit le nombre d'objets réellement créés à un moment donné. Certains paramètres permettent de libérer des pourcentages de la mémoire du tas, mais pour toutes fins pratiques la JVM n'est jamais capable de libérer suffisamment de cette mémoire pour que cela se produise donc aucun autre processus ne peut récupérer cette mémoire, donc le reste du système ne bénéficie pas non plus de la JVM Heap. Un RFE pour cela était "accepté" 29-NOV-2006, mais rien n'a jamais été fait à ce sujet. Ce comportement n'est considéré comme une préoccupation par personne d'autorité.

Il existe une idée fausse selon laquelle la création de nombreux petits objets à vie courte entraîne une pause de la JVM pendant de longues périodes, ce qui est désormais faux également

Les algorithmes GC actuels sont en fait optimisés pour créer de nombreux petits objets de courte durée, c'est essentiellement l'heuristique à 99% pour Java objets dans chaque programme. Les tentatives de Object Pooling rendront la JVM moins performante dans la plupart des cas.

Les seuls objets qui nécessitent un regroupement aujourd'hui sont les objets qui font référence à des ressources finies qui sont externes à la JVM; Sockets, fichiers, connexions à la base de données, etc. et peuvent être réutilisés. Les objets normaux ne peuvent pas être regroupés dans le même sens que dans les langages qui vous permettent un accès direct aux emplacements de mémoire. Objet mise en cache est un concept différent et peut ou non être ce que certaines personnes appellent naïvement pooling, les deux concepts ne sont pas la même chose et ne doivent pas être confondus .

Les algorithmes GC modernes n'ont pas ce problème car ils ne se désallouent pas selon un calendrier, ils se désallouent quand de la mémoire libre est nécessaire dans une certaine génération. Si le tas est suffisamment grand, aucune désallocation ne se produit suffisamment longtemps pour provoquer des pauses.

Les langages dynamiques orientés objet battent C même aujourd'hui sur les tests de calcul sensibles.

484
user7519

Conclusion: ne compromettez pas votre conception afin de prendre des raccourcis pour créer des objets. Évitez de créer des objets inutilement. Si cela est judicieux, concevez pour éviter les opérations redondantes (de toute sorte).

Contrairement à la plupart des réponses - oui, l'allocation d'objet a un coût associé. C'est un faible coût, mais vous devez éviter de créer des objets inutiles. Comme vous devriez éviter tout élément inutile dans votre code. Les grands graphes d'objets ralentissent le GC, impliquent des temps d'exécution plus longs dans la mesure où vous allez probablement avoir plus d'appels de méthode, déclencher plus de ratés du cache du processeur et augmenter la probabilité que votre processus soit échangé sur le disque en faible RAM = cas.

Avant que quiconque ne discute de ce qu'il s'agit d'un cas Edge - j'ai profilé des applications qui, avant d'optimiser, ont créé 20 + Mo d'objets afin de traiter ~ 50 lignes de données. C'est très bien dans les tests, jusqu'à ce que vous augmentiez jusqu'à une centaine de demandes par minute et que vous créez soudainement 2 Go de données par minute. Si vous voulez faire 20 requêtes/sec, vous créez 400 Mo d'objets, puis vous les jetez. 20 reqs/sec est minuscule pour un serveur décent.

97
jasonk

En fait, en raison des stratégies de gestion de la mémoire rendues possibles par le Java (ou tout autre langage géré)), la création d'objets est un peu plus que l'incrémentation d'un pointeur dans un bloc de mémoire appelé la jeune génération. C'est beaucoup plus rapide que C, où une recherche de mémoire libre doit être effectuée.

L'autre partie du coût est la destruction d'objets, mais c'est difficile à comparer à C. Le coût d'une collection est basé sur la quantité d'objets enregistrés à long terme mais la fréquence des collections est basée sur la quantité d'objets créés ... dans au final, c'est toujours beaucoup plus rapide que la gestion de mémoire de style C.

60
amara

D'autres affiches ont souligné à juste titre que la création d'objets est extrêmement rapide en Java et que vous ne devriez généralement pas vous en soucier dans une application normale Java.

Il existe quelques situations très spéciales où est une bonne idée pour éviter la création d'objets.

  • Lorsque vous écrivez une application sensible à la latence et que vous souhaitez éviter les pauses du GC. Plus vous produisez d'objets, plus le GC se produit et plus les chances de pause sont grandes. Cela peut être une considération valable pour les jeux, certaines applications multimédias, le contrôle robotique, le trading à haute fréquence, etc. La solution consiste à pré-allouer tous les objets/tableaux dont vous avez besoin à l'avance et à les réutiliser. Il existe des bibliothèques spécialisées dans la fourniture de ce type de capacité, par exemple Javolution . Mais sans doute, si vous vous souciez vraiment de la faible latence, vous devriez utiliser C/C++/assembler plutôt que Java ou C # :-)
  • Éviter les primitives encadrées (Double, Entier etc.) peut être une micro-optimisation très bénéfique dans certaines circonstances. Étant donné que les primitives non encadrées (double, int, etc.) évitent la surcharge par objet, elles sont un travail intensif beaucoup plus rapide comme le traitement numérique, etc. En règle générale, les tableaux primitifs fonctionnent très bien dans Java si vous voulez pour les utiliser pour le calcul de votre nombre plutôt que pour tout autre type d'objets.
  • Dans les situations de mémoire contrainte , vous voulez minimiser le nombre d'objets (actifs) créés car chaque objet porte une petite surcharge (généralement 8 à 16 octets selon la JVM la mise en oeuvre). Dans de telles circonstances, vous devriez préférer un petit nombre de grands objets ou matrices pour stocker vos données plutôt qu'un grand nombre de petits objets.
38
mikera

Il y a un noyau de vérité dans ce que dit votre collègue. Je suggère respectueusement que le problème avec l'objet creation est en fait une ordure collection. En C++, le programmeur peut contrôler précisément la façon dont la mémoire est désallouée. Le programme peut accumuler du crud aussi longtemps ou aussi court qu'il le souhaite. En outre, le programme C++ peut supprimer le crud en utilisant un thread différent de celui qui l'a créé. Ainsi, le thread qui fonctionne actuellement ne doit jamais s'arrêter pour nettoyer.

En revanche, la machine virtuelle Java JVM) arrête périodiquement votre code pour récupérer la mémoire inutilisée. La plupart des développeurs Java ne remarquent jamais cette pause, car elle est généralement peu fréquente et très court. Plus vous accumulez de saletés ou plus votre JVM est contrainte, plus ces pauses sont fréquentes. Vous pouvez utiliser des outils comme VisualVM pour visualiser ce processus.

Dans les versions récentes de Java, l'algorithme de garbage collection (GC) peut être réglé . En règle générale, plus vous voulez raccourcir les pauses, plus la surcharge de la machine virtuelle est coûteuse (c'est-à-dire que le CPU et la mémoire dépensent pour coordonner le processus GC).

Quand cela pourrait-il être important? Chaque fois que vous vous souciez de taux de réponse inférieurs à la milliseconde, vous vous souciez du GC. Systèmes de trading automatisés écrits en Java ajuste fortement la JVM pour minimiser les pauses. Les entreprises qui écriraient autrement Java se tournent vers C++ dans des situations où les systèmes doivent être très réactifs) tout le temps.

Pour mémoire, je ne tolère pas l'évitement des objets en général! Par défaut, la programmation orientée objet. Ajustez cette approche uniquement si le CPG vous gêne, puis seulement après avoir essayé de régler la JVM pour faire une pause pendant moins de temps. Un bon livre sur Java tuning des performances est Java Performance par Charlie Hunt et Binu John.

17
greg

Il y a un cas où vous pouvez être découragé de créer trop d'objets dans Java à cause de la surcharge - conception pour des performances sur la plateforme Android

En dehors de cela, les réponses ci-dessus sont vraies.

11
Peter Kelly

le GC est réglé pour de nombreux objets de courte durée

cela dit, si vous pouvez réduire de manière triviale l'allocation d'objets, vous devriez

un exemple serait de construire une chaîne dans une boucle, la manière naïve serait

String str = "";
while(someCondition){
    //...
    str+= appendingString;
}

ce qui crée un nouvel objet String sur chaque += opération (plus un StringBuilder et le nouveau tableau de caractères sous-jacent)

vous pouvez facilement réécrire ceci en:

StringBuilder strB = new StringBuilder();
while(someCondition){
    //...
    strB.append(appendingString);
}
String str = strB.toString();

ce modèle (résultat immuable et une valeur intermédiaire mutable locale) peut également être appliqué à d'autres choses

mais à part ça, vous devriez tirer un profileur pour trouver le vrai goulot d'étranglement au lieu de chasser les fantômes

9
ratchet freak

Joshua Bloch (l'un des Java) a écrit dans son livre Java efficace dans 2001:

Éviter la création d'objets en maintenant votre propre pool d'objets est une mauvaise idée sauf si les objets du pool sont extrêmement lourds. Un exemple prototypique d'un objet qui justifie un pool d'objets est une connexion à une base de données. Le coût d'établissement de la connexion est suffisamment élevé pour qu'il soit judicieux de réutiliser ces objets. D'une manière générale, cependant, la maintenance de vos propres pools d'objets encombre votre code, augmente l'empreinte mémoire et nuit aux performances. Les implémentations JVM modernes ont des récupérateurs de place hautement optimisés qui surpassent facilement ces pools d'objets sur des objets légers.

6
Anthony Ananich

Cela dépend vraiment de l'application spécifique, donc c'est vraiment difficile à dire en général. Cependant, je serais assez surpris si la création d'objets était en fait un goulot d'étranglement dans les performances d'une application. Même s'ils sont lents, l'avantage du style de code l'emportera probablement sur les performances (sauf s'il est réellement perceptible par l'utilisateur)

Dans tous les cas, vous ne devriez même pas commencer à vous inquiéter de ces choses avant d'avoir profilé votre code pour déterminer les goulots d'étranglement des performances réelles au lieu de deviner. D'ici là, vous devez faire tout ce qui est le mieux pour la lisibilité du code, pas les performances.

5
Oleksi

Je pense que votre collègue a dû dire du point de vue de la création d'objets inutiles. Je veux dire que si vous créez fréquemment le même objet, il est préférable de partager cet objet. Même dans les cas où la création d'objet est complexe et prend plus de mémoire, vous souhaiterez peut-être cloner cet objet et éviter de créer ce processus de création d'objet complexe (mais cela dépend de vos besoins). Je pense que l'énoncé "La création d'objets coûte cher" doit être pris dans son contexte.

En ce qui concerne les besoins en mémoire JVM, attendez Java 8, vous n'avez même pas besoin de spécifier -Xmx, les paramètres du méta-espace prendront en charge le besoin de mémoire JVM et il se développera automatiquement.

3
AKS

Le GC de Java est en fait très optimisé en termes de création rapide de nombreux objets de façon "éclatée". D'après ce que je peux comprendre, ils utilisent un allocateur séquentiel (le plus rapide et le plus simple O(1) allocateur pour les requêtes de taille variable) pour ce type de "cycle de rafale" dans un espace mémoire qu'ils appellent "Eden l'espace ", et seulement si les objets persistent après un cycle GC, ils sont déplacés vers un endroit où le GC peut les collecter un par un.

Cela dit, si vos besoins en performances deviennent suffisamment critiques (comme mesurés avec les besoins réels de l'utilisateur), les objets entraînent des frais généraux, mais je n'y penserais pas tellement en termes de création/allocation. Cela a plus à voir avec la localité de référence et la taille supplémentaire de tous les objets dans Java tel que requis pour supporter des concepts comme la réflexion et la répartition dynamique (Float est plus grand que float, souvent quelque chose comme 4 fois plus grand sur 64 bits avec ses exigences d'alignement, et un tableau de Float n'est pas nécessairement garanti d'être stocké contigu d'après ce que je comprends).

L'une des choses les plus accrocheuses que j'ai jamais vues développées en Java qui m'a fait considérer comme un concurrent lourd dans mon domaine (VFX) était un traceur de chemin standard interactif et multithread (n'utilisant pas la mise en cache de l'irradiance) ou BDPT ou MLS ou quoi que ce soit d'autre) sur le processeur fournissant des aperçus en temps réel qui ont convergé assez rapidement vers une image sans bruit. J'ai travaillé avec des professionnels en C++ consacrant leur carrière à de telles choses avec des profileurs de fantaisie à la main qui avaient du mal à faire cette.

Mais j'ai regardé le code source et bien qu'il utilisait beaucoup d'objets à un coût trivial, les parties les plus critiques du traceur de chemin (le BVH et les triangles et les matériaux) évitaient très clairement et délibérément les objets en faveur de grands tableaux de types primitifs ( la plupart float[] et int[]), ce qui lui a permis d'utiliser beaucoup moins de mémoire et de garantir la localisation spatiale pour passer d'un float dans le tableau au suivant. Je ne pense pas qu'il soit trop spéculatif de penser que si l'auteur avait utilisé des types encadrés comme Float, cela aurait eu un coût assez élevé pour ses performances. Mais nous parlons de la partie la plus absolument critique de ce moteur, et je suis presque sûr, étant donné l'habileté du développeur à l'optimiser, qu'il l'a mesuré et appliqué cette optimisation très, très judicieusement, car il a utilisé avec plaisir des objets partout ailleurs avec coût insignifiant pour son impressionnant traceur de chemin en temps réel.

Un collègue m'a dit qu'en Java la création d'objets est l'opération la plus coûteuse que vous puissiez effectuer. Je ne peux donc conclure que pour créer le moins d'objets possible.

Même dans un domaine aussi critique que le mien pour la performance, vous n'écrirez pas de produits efficaces si vous vous bloquez dans des endroits sans importance. Je dirais même que les domaines les plus critiques peuvent entraîner une demande de productivité encore plus élevée, car nous avons besoin de tout le temps supplémentaire que nous pouvons obtenir pour régler les points chauds qui comptent vraiment en ne perdant pas ce temps sur des choses qui ne le font pas . Comme avec l'exemple de traceur de chemin ci-dessus, l'auteur a appliqué ces optimisations habilement et judicieusement uniquement aux endroits qui importaient vraiment, vraiment et avec le recul après avoir mesuré, et utilisait toujours avec plaisir des objets partout ailleurs.

1
Dragon Energy

La création d'une classe ne se limite pas à l'allocation de mémoire. Il y a aussi l'initialisation, je ne sais pas pourquoi cette partie n'est pas couverte par toutes les réponses. Les classes normales contiennent des variables et font une certaine forme d'initialisation, et ce n'est pas gratuit. Selon la classe, elle peut lire des fichiers ou effectuer un autre nombre d'opérations lentes.

Il suffit donc de considérer ce que fait le constructeur de classe, avant de déterminer s'il est gratuit ou non.

1
Alex

J'ai fait un rapide microbenchmark à ce sujet et j'ai fourni sources complètes dans github. Ma conclusion est que la création d'objets ne coûte pas cher ou non, mais la création continue d'objets avec l'idée que le GC prendra soin de vous fera que votre application déclenchera le processus GC plus tôt. GC est un processus très coûteux et il est préférable de l'éviter autant que possible et de ne pas essayer de le pousser pour démarrer.

0
Archimedes Trajano

Comme les gens l'ont dit, la création d'objets n'est pas un gros coût en Java (mais je parie plus gros que la plupart des opérations simples comme l'ajout, etc.) et vous ne devriez pas trop l'éviter.

Cela dit, cela reste un coût, et parfois vous pouvez vous retrouver à essayer de supprimer autant d'objets que possible. Mais ce n'est qu'après que le profilage a montré que c'est un problème.

Voici une excellente présentation sur le sujet: https://www.cs.virginia.edu/kim/publicity/pldi09tutorials/memory-efficient-Java-tutorial.pdf

0
rkj