Pourquoi la JVM (Sun) a-t-elle une limite supérieure fixe pour l’utilisation de la mémoire (-Xmx)?

Dans l’esprit de la question Java: Pourquoi MaxPermSize existe-t-il? , Je voudrais demander pourquoi Sun JVM utilise une limite supérieure fixe pour la taille de son pool d’allocation de mémoire.

La valeur par défaut est 1/4 de votre RAM physique (avec limite supérieure et inférieure); Par conséquent, si vous avez une application gourmande en mémoire, vous devez modifier manuellement la limite (paramètre -Xmx), sinon votre application fonctionnera mal, voire tombera en panne avec une erreur OutOfMemoryError.

Pourquoi cette limite fixe existe-t-elle même? Pourquoi la JVM n’alloue-t-elle pas la mémoire selon les besoins, comme le font les programmes natifs sur la plupart des systèmes d’exploitation?

Cela résoudrait toute une série de problèmes courants avec les logiciels Java (google suffit pour voir combien d’indices il y a sur le net pour résoudre les problèmes en définissant -Xmx).

Modifier:

Certaines réponses soulignent que cela protégera le rest du système contre un programme Java avec une fuite de mémoire en cours d’exécution; sans la limite, cela réduirait tout le système en épuisant toute la mémoire. Cela est vrai, cependant, cela est également vrai pour tout autre programme, et les systèmes d’exploitation modernes vous permettent déjà de limiter la mémoire maximale pour un programme (Linux ulimit, Windows “Job Objects”). Donc, cela ne répond pas vraiment à la question, à savoir “Pourquoi la JVM le fait-elle différemment de la plupart des autres programmes / environnements d’exécution?”.

Pourquoi cette limite fixe existe-t-elle même? Pourquoi la JVM n’alloue-t-elle pas la mémoire selon les besoins, comme le font les programmes natifs sur la plupart des systèmes d’exploitation?

La raison n’est PAS que le CPG doit savoir d’avance quelle peut être la taille maximale du tas. La JVM est clairement capable d’étendre son tas … jusqu’au maximum … et je suis sûr que ce serait un changement relativement minime pour supprimer ce maximum. (Après tout, d’autres implémentations Java le font.) Et il serait également possible d’avoir un moyen simple de dire “utiliser autant de mémoire que vous le souhaitez” pour la JVM.

Je suis sûr que la vraie raison est de protéger le système d’exploitation hôte contre les effets des applications Java défectueuses utilisant toute la mémoire disponible. Exécuter avec un tas illimité est potentiellement dangereux.

Fondamentalement, de nombreux systèmes d’exploitation (par exemple Windows, Linux) subissent une grave dégradation des performances si certaines applications essaient d’utiliser toute la mémoire disponible. Sous Linux, par exemple, le système peut mal tourner, ce qui fait que tout le système fonctionne très lentement. Dans le pire des cas, le système ne pourra pas démarrer de nouveaux processus et les processus existants risquent de se bloquer lorsque le système d’exploitation refuse leurs demandes (légitimes) de mémoire supplémentaire. Souvent, la seule option est de redémarrer.

Si la machine virtuelle Java fonctionnait avec un segment de mémoire illimité par défaut, chaque fois que quelqu’un exécutait un programme Java avec une fuite de stockage … ou essayait simplement d’utiliser trop de mémoire … cela risquerait de faire tomber tout le système d’exploitation.

En résumé, avoir un lien de tas par défaut est une bonne chose car:

  • il protège la santé de votre système,
  • il encourage les développeurs / utilisateurs à réfléchir à l’utilisation de la mémoire par les applications “affamées”, et
  • cela permet potentiellement des optimisations de GC. (Comme suggéré par d’autres réponses: c’est plausible, mais je ne peux pas le confirmer.)

MODIFIER

En réponse aux commentaires:

  • Peu importe que les JVM de Sun vivent dans un tas limité, alors que les autres applications ne le font pas. Ils le font, et les avantages sont (IMO) clairs. Peut-être une question plus intéressante est pourquoi les autres langages gérés ne lient pas leurs tas par défaut.

  • Les approches -Xmx et ulimit sont qualitativement différentes. Dans le premier cas, la JVM connaît parfaitement les limites sous lesquelles elle s’exécute et a la possibilité de gérer son utilisation de la mémoire en conséquence. Dans ce dernier cas, la première chose que connaît une application C typique, c’est lorsqu’un appel malloc échoue. La réponse typique est de sortir avec un code d’erreur (si le programme vérifie le résultat malloc ) ou de mourir avec une erreur de segmentation. OK, une application C pourrait en théorie garder une trace de la quantité de mémoire utilisée et essayer de répondre à une crise de mémoire imminente. Mais ce serait difficile.

  • L’autre chose qui est différente dans les applications Java et C / C ++ est que les premières ont tendance à être à la fois plus compliquées et plus longues. En pratique, cela signifie que les applications Java risquent davantage de subir des fuites lentes. Dans le cas du C / C ++, le fait que la gestion de la mémoire soit plus difficile signifie que les développeurs ne tentent pas de créer des applications uniques de cette complexité. Au lieu de cela, ils sont plus susceptibles de construire (par exemple) un service complexe en ayant un processus d’écoute fork de processus enfants pour faire des choses … et puis quitter. Cela atténue naturellement l’effet des memory leaks dans le processus enfant.

  • L’idée d’une JVM répondant «de manière adaptative» aux requêtes de l’OS pour redonner de la mémoire est intéressante. Mais il y a un gros problème. Afin de redonner un segment de mémoire, la machine virtuelle Java doit d’abord supprimer tous les objects accessibles dans le segment. Cela signifie généralement que vous exécutez le ramasse-miettes. Mais exécuter le ramasse-miettes est la dernière chose à faire si le système est en crise de mémoire… car il est à peu près garanti de générer une rafale de mémoire virtuelle.

Hm, je vais essayer de résumer les réponses jusqu’ici.

Il n’y a aucune raison technique pour laquelle la JVM doit avoir une limite matérielle pour sa taille de segment de mémoire. Il aurait pu être implémenté sans un, et de nombreux autres langages dynamics ne l’ont pas.

Par conséquent, atsortingbuer à la machine virtuelle une limite de taille de segment de mémoire était simplement une décision de conception prise par les développeurs. Il est difficile de deviner pourquoi cela a été fait et il n’ya peut-être pas une seule raison. La raison la plus probable est qu’elle aide à protéger un système contre un programme Java avec une fuite de mémoire, qui pourrait sinon épuiser toute la RAM et entraîner le blocage des autres applications ou du système.

Sun aurait pu omettre cette fonctionnalité et simplement dire aux gens d’utiliser les mécanismes de limitation des ressources du système d’exploitation, mais ils souhaitaient probablement toujours avoir une limite, ils l’ont donc implémentée eux-mêmes. En tout état de cause, la JVM doit être consciente de ces limites (pour adapter sa stratégie de GC). L’utilisation d’un mécanisme natif du système d’exploitation n’aurait donc pas permis d’économiser beaucoup d’efforts de programmation.

En outre, il existe une raison pour laquelle une telle limite intégrée est plus importante pour la JVM que pour un programme “normal” sans GC (tel qu’un programme C / C ++):

Contrairement à un programme avec gestion manuelle de la mémoire, un programme utilisant le GC n’a pas vraiment besoin de mémoire bien définie, même avec des données d’entrée fixes. Il ne s’agit que d’une exigence minimale, c’est-à-dire la sum des tailles de tous les objects réellement actifs (accessibles) à un moment donné. Cependant, dans la pratique, un programme aura besoin de mémoire supplémentaire pour contenir les objects morts, mais pas encore GCed, car le GC ne peut pas collecter immédiatement tous les objects, car cela entraînerait une surcharge du GC. Ainsi, le GC n’intervient que de temps en temps, et par conséquent, une “marge de manœuvre” est nécessaire sur le tas, où les objects morts peuvent attendre le GC.

Cela signifie que la mémoire requirejse pour un programme utilisant GC est vraiment un compromis entre économiser de la mémoire et avoir un bon débit (en permettant au CPG de fonctionner moins souvent). Ainsi, dans certains cas, il peut être judicieux de définir une limite de segment de mémoire inférieure à celle que la JVM utiliserait si elle le pouvait, alors économisez de la RAM au désortingment des performances. Pour ce faire, il doit y avoir un moyen de définir une limite de tas.

Je pense qu’une partie de cela a à voir avec la mise en œuvre du Garbage Collector (GC). Le GC est généralement paresseux, ce qui signifie qu’il ne commencera réellement à essayer de récupérer de la mémoire en interne que lorsque le tas est à sa taille maximale. Si vous n’avez pas défini de limite supérieure, le moteur d’exécution continuerait à se gonfler jusqu’à ce qu’il utilise tous les bits de mémoire disponibles sur votre système.

En effet, du sharepoint vue de l’application, il est plus performant de prendre plus de ressources que de déployer des efforts pour utiliser les ressources que vous avez déjà à utiliser pleinement. Cela a un sens pour beaucoup d’utilisations (sinon la plupart) de Java, qui est un paramètre de serveur où l’application est littéralement la seule chose qui compte sur le serveur. Il a tendance à être légèrement moins idéal lorsque vous essayez d’implémenter un client en Java, qui s’exécutera simultanément sur des dizaines d’autres applications.

Rappelez-vous qu’avec les programmes natifs, le programmeur demande généralement mais nettoie explicitement les ressources. Ce n’est généralement pas vrai avec les environnements qui gèrent automatiquement la mémoire.

Cela est dû à la conception de la JVM. D’autres JVM (comme celle de Microsoft et certains IBM) peuvent utiliser toute la mémoire disponible dans le système si nécessaire, sans limite arbitraire.

Je crois que cela permet des optimisations par GC.

Je pense que la limite supérieure de la mémoire est liée au fait que JVM est une VM.

Comme toute machine physique a une quantité donnée de RAM, celle-ci en a une.

La taille maximale facilite la gestion de la JVM par le système d’exploitation et garantit des gains de performances (moins de swap).

Sun ‘JVM fonctionne également dans une architecture matérielle assez limitée (systèmes ARM embarqués) et la gestion des ressources est cruciale.

Une réponse donnée par personne ci-dessus est que la machine virtuelle Java utilise à la fois des pools de mémoire de segment de mémoire et de segment de mémoire. Mettre une limite supérieure sur le tas définit non seulement la quantité de mémoire disponible pour les pools de mémoire de segment de mémoire, mais définit également la quantité de mémoire disponible pour les utilisations NON-HEAP. Je suppose que la JVM pourrait simplement allouer du non-tas en haut de la mémoire virtuelle et du tas au bas de la mémoire virtuelle et croître les uns vers les autres.

La mémoire non-tas comprend les DLL ou SO constituant la JVM et tout code natif utilisé, ainsi que le code Java compilé, les stacks de threads, les objects natifs, PermGen (métadonnées sur les classes compilées), entre autres utilisations. J’ai vu des programmes Java planter parce que le tas contenait tellement de mémoire que l’application était à court de mémoire non-tas. C’est là que j’ai appris qu’il peut être important de réserver de la mémoire pour des utilisations autres que des segments de mémoire en ne définissant pas le tas comme trop volumineux.

Cela fait une différence beaucoup plus grande dans un monde 32 bits où une application ne possède souvent que 2 Go d’espace d’adressage virtuel dans un monde 64 bits, bien sûr.

Ne serait-il pas plus judicieux de séparer la limite supérieure qui déclenche GC et le maximum pouvant être alloué? Une fois que la mémoire allouée atteint la limite supérieure, GC peut lancer et libérer de la mémoire dans le pool libre. un peu comme je nettoie mon bureau que je partage avec mon collègue. J’ai un grand bureau et mon seuil de tolérance sur la table est bien inférieur à celui de mon bureau. Je n’ai pas besoin de remplir tous les pouces disponibles avant de les ramasser.

Je pourrais également retourner une partie de l’espace de bureau que j’utilise à mon collègue, qui partage mon bureau … Je crois comprendre que jvms ne renvoie pas de mémoire au système après l’avoir alloué à lui-même, mais ne doit pas être comme ça non?

Il alloue la mémoire au besoin, jusqu’à -Xmx;)

Une des raisons pour lesquelles je peux penser est qu’une fois que la JVM aura alloué une quantité de mémoire à son tas, elle ne le lâchera jamais. Donc, si votre segment de mémoire n’a pas de limite supérieure, la JVM peut simplement récupérer toute la mémoire disponible sur le système, puis ne jamais la laisser partir.

La limite supérieure indique également à la JVM qu’elle doit effectuer une récupération de place complète. Si votre application est toujours sous la limite supérieure, la machine virtuelle Java reportera le nettoyage de la mémoire et permettra à l’empreinte mémoire de votre application de croître.

Les programmes natifs peuvent également mourir en raison d’erreurs de mémoire insuffisante puisque les applications natives ont également une limite de mémoire: la mémoire disponible sur le système – la mémoire déjà détenue par d’autres applications.

La JVM a également besoin d’un bloc contigu de mémoire système pour que la récupération de place soit effectuée efficacement.

MODIFIER

Revendication de mémoire contiguë ou ici

La JVM laissera apparemment de la mémoire, mais elle est rare avec la configuration par défaut.