Pourquoi la taille du segment de mémoire est-elle fixée sur les machines virtuelles Java?


20

Quelqu'un peut-il m'expliquer pourquoi les machines virtuelles Java (je n'en ai pas vérifié trop, mais je n'en ai jamais vu une qui ne l'a pas fait de cette façon) doivent fonctionner sur une taille de segment fixe? Je sais qu'il est plus facile à implémenter sur un simple tas contigu, mais la JVM Sun a maintenant plus de dix ans, alors je m'attendrais à ce qu'ils aient eu le temps d'améliorer cela.

La nécessité de définir la taille maximale de la mémoire de votre programme au démarrage semble une telle chose dans les années 1960, puis il y a les mauvaises interactions avec la gestion de la mémoire virtuelle du système d'exploitation (récupération des données échangées par le GC, incapacité à déterminer la quantité de mémoire du processus Java) en utilisant vraiment du côté du système d'exploitation, d'énormes quantités d'espace virtuel gaspillées (je sais, vous ne vous souciez pas de vos fantastiques machines 48 bits ...)). Je suppose également que les diverses tentatives tristes de construire de petits systèmes d'exploitation à l'intérieur de la JVM (serveurs d'applications EE, OSGi) sont au moins en partie responsables de cette circonstance, car l'exécution de plusieurs processus Java sur un système entraîne invariablement un gaspillage de ressources car vous devez donnez à chacun d'eux la mémoire qu'il pourrait avoir à utiliser au maximum.

Étonnamment, Google n'a pas provoqué les tempêtes d'indignation que j'attendrais à ce sujet, mais ils ont peut-être été enterrés sous les millions de personnes découvrant la taille fixe du tas et l'acceptant juste pour un fait.


La conception des serveurs d'applications EE serait parfaitement logique même sans cette "circonstance", car la machine virtuelle Java elle-même a besoin d'espace, et la commutation entre les threads est moins coûteuse que la commutation entre les processus - c'est l'une des choses qui ont fait de Java un grand acteur à la fin des années 90.
Michael Borgwardt

C'est un bon coup de gueule, et je me demande à quel point vous y avez réfléchi. Par exemple, comment changerait l'interaction GC / swap si vous n'aviez pas de limite de tas? Comment l'espace VM est-il gaspillé? Vous obtenez votre espace 2 / 3Gb que vous l'utilisiez ou non, et si vous repoussez les limites de cet espace, peu importe que vous ayez un tas fixe ou flottant. D'ailleurs, comment plusieurs machines JVM gaspillent-elles autre chose que le swap (qui doit être configuré de manière appropriée aux fins de la machine)?
kdgregory

2
C'est une sorte de diatribe, mais il est informé par des années d'expérience quotidienne dans l'écriture et l'exploitation d'une plate-forme basée sur Java. Si vous ne faites pas d'échange (car cela rendra votre système insensible pendant 20 minutes jusqu'à ce que le processus d'emballement manque d'espace à allouer) et que la surcharge de mémoire soit désactivée pour des raisons de stabilité (le tueur OOM n'est pas très bon pour sélectionner les victimes ), vous vous souciez de l'espace VM, et contrairement à ce que les gens ci-dessous impliquent, le lancement d'une machine virtuelle Java avec -Xmx2048m allouera immédiatement 2 Go de mémoire virtuelle (au moins dans ma JVM Sun sur Linux) pour un programme avec une variable.
themel

Excellente question. Je me demandais la même chose. Mais lesquels des "faits" présentés ici en q et les réponses sont correctes?
Martin Ba

Pour les réactions que vous recherchiez, il vous suffit de vous diriger vers le tracker de bug solaire ... par exemple ici , ici et ici . Lisez-les et ressentez la rage indignée :)
Basic

Réponses:


23

Vous avez tort. La taille de segment de mémoire des machines virtuelles Java n'est pas fixe, uniquement limitée:

  • -Xmx définit la taille de mémoire maximale du tas
  • -Xms définit la taille de mémoire minimale du tas

La fixation d'une limite supérieure est nécessaire pour plusieurs raisons. Tout d'abord, il indique au ramasse-miettes quand il doit entrer en action. Deuxièmement, cela empêche la JVM de boucher la machine entière en consommant trop de mémoire. La taille de segment minimale est probablement utile pour réserver au moins la quantité de mémoire dont le programme a besoin, pour éviter qu'il ne manque de mémoire (car d'autres processus consomment trop).


9
non le min est la valeur par défaut pour éviter un démarrage lent quand beaucoup doit être alloué, ce qui entraîne des augmentations de tas répétées, la pagination traitera le ram physique qui s'épuise
cliquet freak

@ratchetfreak qui était ma deuxième supposition ;-)
user281377

@ user281377 Si tel est le cas, comment C # peut-il fonctionner correctement sans une taille de mémoire maximale?
cmorse

cmorse: Je ne peux que deviner. Peut-être que Java est destiné aux grands serveurs, où de nombreuses applications partagent des ressources et des limites strictement appliquées sont souhaitables, tandis que .net est plutôt conçu pour les PC et les serveurs plus petits et plus dédiés.
user281377

@ user281377: Les applications Java que j'ai utilisées et qui manquent d'espace de stockage le gèrent généralement très mal, se bloquant généralement ou étant très floconneuses par la suite. Et ASP.net fonctionne très bien sur les petits et les grands serveurs. Ce que je ne comprends vraiment pas, c'est pourquoi par défaut, Java applique cette limite. J'adorerais entendre le raisonnement derrière leur décision ... Je suis sûr qu'ils avaient une bonne raison.
cmorse

6

J'imagine que la réponse a quelque chose à voir avec l'héritage de Java. Il a été initialement conçu comme un langage à utiliser pour les systèmes embarqués, où les ressources sont évidemment limitées et vous ne voulez pas que les processus engloutissent simplement tout ce qui est disponible. Il facilite également l'administration du système, car il facilite la mise à disposition des ressources sur un serveur si vous pouvez définir des limites de ressources. Je vois que les dernières machines virtuelles Java semblent utiliser plusieurs tas non continus, bien que tout cela apparaisse bien sûr comme un seul tas à votre code.

(FWIW, vous deviez spécifier les besoins en mémoire d'un programme sous les versions MacOS pré-Darwin d'Apple [jusqu'au système 7 de toute façon, qui était la dernière que j'ai utilisée], ce qui était bien dans les années 80.)


1
+1 - pour être (1) la seule réponse qui a réellement répondu à la question et (2) être plausible.
kdgregory

2

Vous devez donner le GC certains mécanisme pour lui dire quand s'exécuter, ou votre programme remplira tout l'espace mémoire virtuel. Il existe de nombreuses autres façons de déclencher le GC: le temps écoulé, le nombre d'allocations, le nombre d'affectations, probablement d'autres auxquelles je ne peux pas penser en ce moment. IMO, rien de tout cela n'est aussi bon que de simplement définir une limite de mémoire et d'exécuter GC lorsque l'espace alloué atteint cette limite.

La clé est de définir les limites correctes . Je regarde -ms"c'est la quantité de mémoire dont mon application a besoin", et-mx que "elle ne doit jamais dépasser ce montant". Dans un déploiement en production, les deux doivent être proches sinon égaux, et ils doivent être basés sur des exigences réelles et mesurées.

Vos inquiétudes concernant la mémoire virtuelle «gaspillée» sont déplacées: c'est virtuel, c'est (presque) gratuit. Oui, si le segment est trop volumineux, cela signifie que vous ne pouvez pas démarrer autant de threads ou charger autant de fichiers mappés en mémoire. Mais cela fait partie de la conception de l'application: vous avez des ressources rares, vous devez les partitionner de manière à permettre l'exécution de votre application. Dans un tas "de style C", qui se développera jusqu'à ce que vous atteigniez le sommet de la mémoire, le problème de base est le même, vous n'avez juste pas à y penser jusqu'à ce que vous ayez des ennuis.

La seule chose qu'un grand tas peut "gaspiller" est l'espace de swap, car tous les segments inscriptibles nécessitent un engagement de swap. Mais cela fait partie de la conception du système: si vous voulez que de nombreuses machines virtuelles Java s'exécutent sur la même boîte, augmentez votre swap ou réduisez leur allocation de tas. S'ils commencent à se débattre, alors vous essayez d'en faire trop avec le système; acheter plus de mémoire (et si vous utilisez toujours un processeur 32 bits, achetez un nouveau boîtier).


1
Mais le seuil pour exécuter GC n'a rien à voir avec un seuil fixe que la mémoire totale utilisée par le programme ne peut pas dépasser. (En effet, cela ne devrait probablement pas; si vous avez un gros tas et que vous ne pouvez que GC lorsqu'il est plein, vous avez nécessairement des périodes de GC rares mais longues). Voir collection de déchets générationnelle.
Ben

@Ben - oui, vous avez raison. Et ma deuxième phrase souligne qu'il existe des alternatives. Cependant, je ne suis pas d'accord pour dire qu'un tas de taille fixe est la mauvaise façon de gérer GC dans le cas général. Une JVM correctement réglée utilise la "bonne" taille de segment de mémoire; d'après mon expérience, de longues pauses GC se produisent lorsque la JVM n'a pas été correctement réglée. Et souvent, la «bonne» taille de tas est beaucoup plus petite que vous ne le pensez.
kdgregory

-2

Comme le dit user281377, vous spécifiez uniquement une limite supérieure de la quantité de mémoire que votre processus peut consommer. Bien sûr, l'application elle-même n'accaparera que l'espace dont elle a besoin.

Qu'il existe ou non une limite supérieure par défaut est une autre question, à la fois pour et contre.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.