Pourquoi la taille primitive booléenne de Java n'est-elle pas définie?

111

La spécification de la machine virtuelle Java indique que la prise en charge des types primitifs booléens est limitée .

Il n'y a pas d'instructions de machine virtuelle Java uniquement dédiées aux opérations sur des valeurs booléennes. Au lieu de cela, les expressions du langage de programmation Java qui fonctionnent sur des valeurs booléennes sont compilées pour utiliser des valeurs du type de données Java virtual machine int.

Ce qui précède implique (bien que je puisse l'avoir mal interprété) que le type de données int est utilisé lors du fonctionnement sur des booléens, mais il s'agit d'une construction de mémoire 32 bits. Étant donné qu'un booléen ne représente qu'un bit d'information:

  • Pourquoi un octet, ou un type court, n'est-il pas utilisé comme proxy pour un booléen au lieu de int?
  • Pour une JVM donnée, quel est le moyen le plus fiable de savoir exactement combien de mémoire est utilisée pour stocker un type booléen?
Joël
la source

Réponses:

116

Réponse courte: oui, les valeurs booléennes sont manipulées comme des entités 32 bits, mais les tableaux de booléens utilisent 1 octet par élément.

Réponse plus longue: la JVM utilise une cellule de pile de 32 bits, utilisée pour contenir des variables locales, des arguments de méthode et des valeurs d'expression. Les primitives inférieures à 1 cellule sont remplies, les primitives supérieures à 32 bits (longues et doubles) prennent 2 cellules. Cette technique minimise le nombre d'opcodes, mais a des effets secondaires particuliers (comme la nécessité de masquer des octets).

Les primitives stockées dans des tableaux peuvent utiliser moins de 32 bits, et il existe différents opcodes pour charger et stocker les valeurs primitives d'un tableau. Les valeurs booléennes et octets utilisent toutes les deux les opcodes baloadet bastore, ce qui implique que les tableaux booléens prennent 1 octet par élément.

En ce qui concerne la disposition des objets en mémoire, cela est couvert par les règles de «mise en œuvre privée» , il peut s'agir de 1 bit, 1 octet ou, comme l'a noté une autre affiche, aligné sur une limite de double mot de 64 bits. Très probablement, il prend la taille de mot de base du matériel sous-jacent (32 ou 64 bits).


En ce qui concerne la minimisation de l'espace utilisé par les booléens: ce n'est vraiment pas un problème pour la plupart des applications. Les cadres de pile (contenant des variables locales et des arguments de méthode) ne sont pas très volumineux, et dans le grand schéma, un booléen discret dans un objet n'est pas non plus si grand. Si vous avez beaucoup d'objets avec beaucoup de booléens, alors vous pouvez utiliser des champs de bits qui sont gérés via vos getters et setters. Cependant, vous paierez une pénalité en temps CPU qui est probablement plus grande que la pénalité en mémoire.

kdgregory
la source
Pour les membres de classe booléens / octets, est-il également vrai qu'ils font également 4 octets? L'instance de classe est allouée dans son ensemble sur la pile, donc je peux imaginer, JVM devrait probablement utiliser 1 octet par membre booléen / octet et enfin faire un alignement de 4 octets pour l'instance de classe complète. Est-ce vrai? (si vous avez des références qui le prouvent, merci de partager)
dma_k
@dma_k: comme indiqué dans ma réponse, la disposition d'une instance de classe dépend de l'implémentation. Cependant, notez que les instances de classe ne sont pas stockées dans la pile, elles sont stockées sur le tas (bien que vous verrez quelques références à "l'analyse d'échappement" du JDK 7 déplaçant des objets de pile en tas, cela ne semble pas être le cas; voir java.sun.com/javase/7/docs/technotes/guides/vm/…)
kdgregory
1
Parfois, l'emballage des booléens peut être en fait plus rapide. Chaque fois que la taille du cache compte, il peut être préférable d'emballer les choses. Par exemple, un tamis principal segmenté fonctionne par blocs de 32 Ko (taille du cache L1) est bien plus rapide qu'un tamis non segmenté. Il y a des frais généraux entre les morceaux et avec l'emballage, vous payez les frais généraux huit fois moins souvent. Je ne l'ai pas encore mesuré.
maaartinus
7

Un seul booléen quelque part dans la hiérarchie d'héritage peut utiliser jusqu'à 8 octets! Cela est dû au rembourrage. Plus de détails peuvent être trouvés dans Quelle quantité de mémoire est utilisée par mon objet Java? :

Pour en revenir à la question de savoir combien un booléen consomme, oui, il consomme au moins un octet, mais en raison des règles d'alignement, il peut en consommer beaucoup plus. À mon humble avis, il est plus intéressant de savoir qu'un booléen [] consommera un octet par entrée et non un bit, plus une surcharge due à l'alignement et au champ de taille du tableau. Il existe des algorithmes de graphes où de grands champs de bits sont utiles, et vous devez être conscient que, si vous utilisez un booléen [], vous avez besoin de presque exactement 8 fois plus de mémoire que nécessaire (1 octet contre 1 bit).

Akuhn
la source
Comment utiliserait un booléen [] de toute façon?
Thomas Jung
boolean [] pourrait être utilisé pour un masque. Parfois, un BitSet peut être meilleur, car il a des méthodes utiles.
Michael Munsey
5

La 5e édition de Java en bref (O'Reilly) indique qu'un type primitif booléen est de 1 octet. Cela pourrait être faux, sur la base de ce que montre l'examen du tas. Je me demande si la plupart des machines virtuelles Java ont des problèmes avec l'allocation de moins d'un octet pour les variables.

Matthew Flynn
la source
3

Le mappage booléen a été fait avec un processeur 32 bits à l'esprit. La valeur int a 32 bits et peut donc être traitée en une seule opération.

Voici une solution de la IAQ Java de Peter Norvig: Questions rarement répondues pour mesurer la taille (avec une certaine imprécision):

static Runtime runtime = Runtime.getRuntime();
...
long start, end;
Object obj;
runtime.gc();
start = runtime.freememory();
obj = new Object(); // Or whatever you want to look at
end =  runtime.freememory();
System.out.println("That took " + (start-end) + " bytes.");
Thomas Jung
la source
Étant donné que cette conversation concerne les primitives, vous devrez faire preuve de créativité pour tester cela, car les primitives ne sont pas stockées dans le tas à moins qu'elles ne soient un champ sur une instance ou un tableau. Et aucun de ceux-ci ne répond à la question de savoir comment Java choisira de le stocker dans la pile de toute façon.
Jesse
2

Les processeurs fonctionnent sur une longueur de type de données spécifique. Dans le cas des processeurs 32 bits, ils ont une longueur de 32 bits et donc ce que vous appelez «int» en Java. Tout ce qui est en dessous ou au-dessus qui doit être rempli ou divisé à cette longueur avant que le CPU puisse le traiter. Cela ne prend pas beaucoup de temps, mais si vous avez besoin de 2 cycles CPU au lieu de 1 pour les opérations de base, cela signifie doubler les coûts / temps.

Cette spécification est dédiée aux processeurs 32 bits afin qu'ils puissent traiter les booléens avec leur type de données natif.

Vous ne pouvez en avoir qu'un ici: vitesse ou mémoire - SUN a choisi la vitesse.

Codé en dur
la source
1

Boolean représente un peu d'informations, mais sa "taille" n'est pas définie avec précision, disent les didacticiels Sun Java. Les littéraux booléens n'ont que deux valeurs possibles: true et false. Voir Types de données Java pour plus de détails.

Krishan
la source
-10

Pourquoi ne pas créer un fichier .java comme celui-ci:

Vide.java

class Empty{
}

et une classe comme celle-ci:

NotEmpty.java

class NotEmpty{
   boolean b;
}

Compilez-les tous les deux et comparez les fichiers .class avec un éditeur hexadécimal.

mring
la source
5
il s'agit d'une autre métrique, sans rapport avec le dimensionnement du type booléen primitif en mémoire.
Joel