En Java, quelle est la meilleure façon de déterminer la taille d’un objet ?
Question
Par exemple, disons que j'ai une application capable de lire un fichier CSV contenant des piles de lignes de données.Je donne à l'utilisateur un résumé du nombre de lignes en fonction des types de données, mais je veux m'assurer de ne pas lire trop de lignes de données et provoquer OutOfMemoryError
s.Chaque ligne se traduit par un objet.Existe-t-il un moyen simple de connaître la taille de cet objet par programme ?Existe-t-il une référence qui définit la taille des types primitifs et des références d'objet pour un VM
?
En ce moment, j'ai un code qui dit lire jusqu'à 32 000 lignes, mais j'aimerais aussi avoir du code qui dit de lire autant de lignes que possible jusqu'à ce que j'aie utilisé 32 Mo de mémoire.C'est peut-être une autre question, mais j'aimerais quand même savoir.
La solution
Vous pouvez utiliser le paquet java.lang.instrument
Compilez et mettez cette classe dans un JAR :
import java.lang.instrument.Instrumentation;
public class ObjectSizeFetcher {
private static Instrumentation instrumentation;
public static void premain(String args, Instrumentation inst) {
instrumentation = inst;
}
public static long getObjectSize(Object o) {
return instrumentation.getObjectSize(o);
}
}
Ajoutez ce qui suit à votre MANIFEST.MF
:
Premain-Class: ObjectSizeFetcher
Utilisez getObjectSize :
public class C {
private int x;
private int y;
public static void main(String [] args) {
System.out.println(ObjectSizeFetcher.getObjectSize(new C()));
}
}
Invoquer avec :
java -javaagent:ObjectSizeFetcherAgent.jar C
Autres conseils
Tu devrais utiliser joyeux, un outil développé dans le cadre du projet OpenJDK.
JOL (Java Object Layout) est la petite boîte à outils permettant d'analyser les schémas de disposition des objets dans les JVM.Ces outils utilisent largement Unsafe, JVMTI et Serviceability Agent (SA) pour décoder la disposition, l'empreinte et les références réelles des objets.Cela rend JOL beaucoup plus précis que d'autres outils reposant sur des vidages de tas, des hypothèses de spécification, etc.
Pour obtenir les tailles des primitives, des références et des éléments du tableau, utilisez VMSupport.vmDetails()
.Sur Oracle JDK 1.8.0_40 exécuté sur Windows 64 bits (utilisé pour tous les exemples suivants), cette méthode renvoie
Running 64-bit HotSpot VM.
Using compressed oop with 0-bit shift.
Using compressed klass with 3-bit shift.
Objects are 8 bytes aligned.
Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
Vous pouvez obtenir la taille réduite d'une instance d'objet en utilisant ClassLayout.parseClass(Foo.class).toPrintable()
(en passant éventuellement une instance à toPrintable
).Il s'agit uniquement de l'espace consommé par une seule instance de cette classe ;il n'inclut aucun autre objet référencé par cette classe.Il fait inclure la surcharge de la VM pour l'en-tête de l'objet, l'alignement des champs et le remplissage.Pour java.util.regex.Pattern
:
java.util.regex.Pattern object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (0000 0001 0000 0000 0000 0000 0000 0000)
4 4 (object header) 00 00 00 00 (0000 0000 0000 0000 0000 0000 0000 0000)
8 4 (object header) cb cf 00 20 (1100 1011 1100 1111 0000 0000 0010 0000)
12 4 int Pattern.flags 0
16 4 int Pattern.capturingGroupCount 1
20 4 int Pattern.localCount 0
24 4 int Pattern.cursor 48
28 4 int Pattern.patternLength 0
32 1 boolean Pattern.compiled true
33 1 boolean Pattern.hasSupplementary false
34 2 (alignment/padding gap) N/A
36 4 String Pattern.pattern (object)
40 4 String Pattern.normalizedPattern (object)
44 4 Node Pattern.root (object)
48 4 Node Pattern.matchRoot (object)
52 4 int[] Pattern.buffer null
56 4 Map Pattern.namedGroups null
60 4 GroupHead[] Pattern.groupNodes null
64 4 int[] Pattern.temp null
68 4 (loss due to the next object alignment)
Instance size: 72 bytes (reported by Instrumentation API)
Space losses: 2 bytes internal + 4 bytes external = 6 bytes total
Vous pouvez obtenir une vue récapitulative de la taille profonde d'une instance d'objet en utilisant GraphLayout.parseInstance(obj).toFootprint()
.Bien sûr, certains objets de l'empreinte peuvent être partagés (également référencés à partir d'autres objets), il s'agit donc d'une surapproximation de l'espace qui pourrait être récupéré lorsque cet objet est récupéré.Pour le résultat de Pattern.compile("^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$")
(pris à partir de cette réponse), jol rapporte une empreinte totale de 1 840 octets, dont seulement 72 sont l'instance Pattern elle-même.
java.util.regex.Pattern instance footprint:
COUNT AVG SUM DESCRIPTION
1 112 112 [C
3 272 816 [Z
1 24 24 java.lang.String
1 72 72 java.util.regex.Pattern
9 24 216 java.util.regex.Pattern$1
13 24 312 java.util.regex.Pattern$5
1 16 16 java.util.regex.Pattern$Begin
3 24 72 java.util.regex.Pattern$BitClass
3 32 96 java.util.regex.Pattern$Curly
1 24 24 java.util.regex.Pattern$Dollar
1 16 16 java.util.regex.Pattern$LastNode
1 16 16 java.util.regex.Pattern$Node
2 24 48 java.util.regex.Pattern$Single
40 1840 (total)
Si vous utilisez plutôt GraphLayout.parseInstance(obj).toPrintable()
, jol vous indiquera l'adresse, la taille, le type, la valeur et le chemin des déréférences de champ vers chaque objet référencé, bien que ce soit généralement trop de détails pour être utile.Pour l’exemple de modèle en cours, vous pourriez obtenir ce qui suit.(Les adresses changeront probablement entre les exécutions.)
java.util.regex.Pattern object externals:
ADDRESS SIZE TYPE PATH VALUE
d5e5f290 16 java.util.regex.Pattern$Node .root.next.atom.next (object)
d5e5f2a0 120 (something else) (somewhere else) (something else)
d5e5f318 16 java.util.regex.Pattern$LastNode .root.next.next.next.next.next.next.next (object)
d5e5f328 21664 (something else) (somewhere else) (something else)
d5e647c8 24 java.lang.String .pattern (object)
d5e647e0 112 [C .pattern.value [^, [, a, -, z, A, -, Z, 0, -, 9, _, ., +, -, ], +, @, [, a, -, z, A, -, Z, 0, -, 9, -, ], +, \, ., [, a, -, z, A, -, Z, 0, -, 9, -, ., ], +, $]
d5e64850 448 (something else) (somewhere else) (something else)
d5e64a10 72 java.util.regex.Pattern (object)
d5e64a58 416 (something else) (somewhere else) (something else)
d5e64bf8 16 java.util.regex.Pattern$Begin .root (object)
d5e64c08 24 java.util.regex.Pattern$BitClass .root.next.atom.val$rhs (object)
d5e64c20 272 [Z .root.next.atom.val$rhs.bits [false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, false, true, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false]
d5e64d30 24 java.util.regex.Pattern$1 .root.next.atom.val$lhs.val$lhs.val$lhs.val$lhs.val$lhs.val$lhs (object)
d5e64d48 24 java.util.regex.Pattern$1 .root.next.atom.val$lhs.val$lhs.val$lhs.val$lhs.val$lhs.val$rhs (object)
d5e64d60 24 java.util.regex.Pattern$5 .root.next.atom.val$lhs.val$lhs.val$lhs.val$lhs.val$lhs (object)
d5e64d78 24 java.util.regex.Pattern$1 .root.next.atom.val$lhs.val$lhs.val$lhs.val$lhs.val$rhs (object)
d5e64d90 24 java.util.regex.Pattern$5 .root.next.atom.val$lhs.val$lhs.val$lhs.val$lhs (object)
d5e64da8 24 java.util.regex.Pattern$5 .root.next.atom.val$lhs.val$lhs.val$lhs (object)
d5e64dc0 24 java.util.regex.Pattern$5 .root.next.atom.val$lhs.val$lhs (object)
d5e64dd8 24 java.util.regex.Pattern$5 .root.next.atom.val$lhs (object)
d5e64df0 24 java.util.regex.Pattern$5 .root.next.atom (object)
d5e64e08 32 java.util.regex.Pattern$Curly .root.next (object)
d5e64e28 24 java.util.regex.Pattern$Single .root.next.next (object)
d5e64e40 24 java.util.regex.Pattern$BitClass .root.next.next.next.atom.val$rhs (object)
d5e64e58 272 [Z .root.next.next.next.atom.val$rhs.bits [false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false]
d5e64f68 24 java.util.regex.Pattern$1 .root.next.next.next.atom.val$lhs.val$lhs.val$lhs (object)
d5e64f80 24 java.util.regex.Pattern$1 .root.next.next.next.atom.val$lhs.val$lhs.val$rhs (object)
d5e64f98 24 java.util.regex.Pattern$5 .root.next.next.next.atom.val$lhs.val$lhs (object)
d5e64fb0 24 java.util.regex.Pattern$1 .root.next.next.next.atom.val$lhs.val$rhs (object)
d5e64fc8 24 java.util.regex.Pattern$5 .root.next.next.next.atom.val$lhs (object)
d5e64fe0 24 java.util.regex.Pattern$5 .root.next.next.next.atom (object)
d5e64ff8 32 java.util.regex.Pattern$Curly .root.next.next.next (object)
d5e65018 24 java.util.regex.Pattern$Single .root.next.next.next.next (object)
d5e65030 24 java.util.regex.Pattern$BitClass .root.next.next.next.next.next.atom.val$rhs (object)
d5e65048 272 [Z .root.next.next.next.next.next.atom.val$rhs.bits [false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false]
d5e65158 24 java.util.regex.Pattern$1 .root.next.next.next.next.next.atom.val$lhs.val$lhs.val$lhs.val$lhs (object)
d5e65170 24 java.util.regex.Pattern$1 .root.next.next.next.next.next.atom.val$lhs.val$lhs.val$lhs.val$rhs (object)
d5e65188 24 java.util.regex.Pattern$5 .root.next.next.next.next.next.atom.val$lhs.val$lhs.val$lhs (object)
d5e651a0 24 java.util.regex.Pattern$1 .root.next.next.next.next.next.atom.val$lhs.val$lhs.val$rhs (object)
d5e651b8 24 java.util.regex.Pattern$5 .root.next.next.next.next.next.atom.val$lhs.val$lhs (object)
d5e651d0 24 java.util.regex.Pattern$5 .root.next.next.next.next.next.atom.val$lhs (object)
d5e651e8 24 java.util.regex.Pattern$5 .root.next.next.next.next.next.atom (object)
d5e65200 32 java.util.regex.Pattern$Curly .root.next.next.next.next.next (object)
d5e65220 120 (something else) (somewhere else) (something else)
d5e65298 24 java.util.regex.Pattern$Dollar .root.next.next.next.next.next.next (object)
Les entrées "(autre chose)" décrire d'autres objets dans le tas qui ne font pas partie de ce graphe d'objets.
La meilleure documentation amusante est la échantillons sympas dans le dépôt jol.Les exemples illustrent les opérations jol courantes et montrent comment vous pouvez utiliser jol pour analyser les composants internes de la VM et du garbage collector.
Il y a quelques années, Javaworld avait un article sur la détermination de la taille des objets Java composites et potentiellement imbriqués, ils expliquent essentiellement la création d'une implémentation sizeof() en Java.L'approche s'appuie essentiellement sur d'autres travaux dans lesquels des personnes ont identifié expérimentalement la taille des primitives et des objets Java typiques, puis ont appliqué ces connaissances à une méthode qui parcourt de manière récursive un graphe d'objets pour calculer la taille totale.
Cela sera toujours un peu moins précis qu'une implémentation native en C simplement à cause des choses qui se passent dans les coulisses d'une classe, mais cela devrait être un bon indicateur.
Alternativement, un projet SourceForge appelé à juste titre taille de qui propose une bibliothèque Java5 avec une implémentation sizeof().
P.S.N'utilisez pas l'approche de sérialisation, il n'y a aucune corrélation entre la taille d'un objet sérialisé et la quantité de mémoire qu'il consomme lorsqu'il est actif.
Premièrement, « la taille d'un objet » n'est pas un concept bien défini en Java.Vous pourriez désigner l'objet lui-même, avec uniquement ses membres, l'objet et tous les objets auxquels il fait référence (le graphe de référence).Vous pouvez parler de la taille en mémoire ou de la taille sur le disque.Et la JVM est autorisée à optimiser des choses comme les chaînes.
Donc la seule bonne façon est de demander à la JVM, avec un bon profileur (j'utilise VotreKit), ce qui n'est probablement pas ce que vous souhaitez.
Cependant, d'après la description ci-dessus, il semble que chaque ligne sera autonome et n'aura pas de grande arborescence de dépendances, donc la méthode de sérialisation sera probablement une bonne approximation sur la plupart des JVM.La façon la plus simple de procéder est la suivante :
Serializable ser;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(ser);
oos.close();
return baos.size();
N'oubliez pas que si vous avez des objets avec des références communes, ceci Ne fera pas donne le résultat correct, et la taille de la sérialisation ne correspondra pas toujours à la taille en mémoire, mais c'est une bonne approximation.Le code sera un peu plus efficace si vous initialisez la taille de ByteArrayOutputStream à une valeur raisonnable.
J'ai accidentellement trouvé une classe Java "jdk.nashorn.internal.ir.debug.objectsizcalculator", déjà dans JDK, qui est facile à utiliser et semble très utile pour déterminer la taille d'un objet.
System.out.println(ObjectSizeCalculator.getObjectSize(new gnu.trove.map.hash.TObjectIntHashMap<String>(12000, 0.6f, -1)));
System.out.println(ObjectSizeCalculator.getObjectSize(new HashMap<String, Integer>(100000)));
System.out.println(ObjectSizeCalculator.getObjectSize(3));
System.out.println(ObjectSizeCalculator.getObjectSize(new int[]{1, 2, 3, 4, 5, 6, 7 }));
System.out.println(ObjectSizeCalculator.getObjectSize(new int[100]));
résultats:
164192
48
16
48
416
Si vous souhaitez simplement savoir combien de mémoire est utilisée dans votre JVM et quelle quantité est libre, vous pouvez essayer quelque chose comme ceci :
// Get current size of heap in bytes
long heapSize = Runtime.getRuntime().totalMemory();
// Get maximum size of heap in bytes. The heap cannot grow beyond this size.
// Any attempt will result in an OutOfMemoryException.
long heapMaxSize = Runtime.getRuntime().maxMemory();
// Get amount of free memory within the heap in bytes. This size will increase
// after garbage collection and decrease as new objects are created.
long heapFreeSize = Runtime.getRuntime().freeMemory();
modifier:J'ai pensé que cela pourrait être utile, car l'auteur de la question a également déclaré qu'il aimerait avoir une logique capable de "lire autant de lignes que possible jusqu'à ce que j'utilise 32 Mo de mémoire".
À l'époque où je travaillais sur Twitter, j'ai écrit un utilitaire permettant de calculer la taille profonde des objets.Il prend en compte différents modèles de mémoire (32 bits, oups compressés, 64 bits), le remplissage, le remplissage de sous-classe, fonctionne correctement sur les structures de données circulaires et les tableaux.Vous pouvez simplement compiler ce fichier .java ;il n'a pas de dépendances externes :
La plupart des autres réponses fournissent des tailles peu profondes - par ex.la taille d'un HashMap sans aucune clé ou valeur, ce qui n'est probablement pas ce que vous souhaitez.
Le projet jamm utilise le package java.lang.instrumentation ci-dessus mais parcourt l'arborescence et peut ainsi vous permettre d'utiliser la mémoire en profondeur.
new MemoryMeter().measureDeep(myHashMap);
https://github.com/jbellis/jamm
Pour utiliser MemoryMeter, démarrez la JVM avec "-javaagent:/jamm.jar"
Vous devez parcourir les objets en utilisant la réflexion.Soyez prudent lorsque vous le faites :
- Le simple fait d'allouer un objet entraîne une certaine surcharge dans la JVM.Le montant varie selon la JVM, vous pouvez donc faire de cette valeur un paramètre.Faites-en au moins une constante (8 octets ?) et appliquez-la à tout ce qui est alloué.
- Juste parce que
byte
est théoriquement de 1 octet ne signifie pas qu'il n'en faut qu'un en mémoire. - Il y aura des boucles dans les références d'objets, vous devrez donc conserver un
HashMap
ou quelque chose comme ça en utilisant des objets égaux comme comparateur pour éliminer les boucles infinies.
@jodonnell :J'aime la simplicité de votre solution, mais de nombreux objets ne sont pas sérialisables (cela générerait donc une exception), les champs peuvent être transitoires et les objets peuvent remplacer les méthodes standard.
Vous devez le mesurer avec un outil, ou l'estimer à la main, et cela dépend de la JVM que vous utilisez.
Il y a une surcharge fixe par objet.C'est spécifique à la JVM, mais j'estime généralement 40 octets.Ensuite, il faut regarder les membres de la classe.Les références d'objet sont de 4 (8) octets dans une JVM 32 bits (64 bits).Les types primitifs sont :
- booléen et octet :1 octet
- char et court :2 octets
- int et flottant :4 octets
- long et double :8 octets
Les tableaux suivent les mêmes règles ;c'est-à-dire qu'il s'agit d'une référence d'objet qui prend donc 4 (ou 8) octets dans votre objet, puis sa longueur multipliée par la taille de son élément.
Essayer de le faire par programme avec des appels à Runtime.freeMemory()
ne vous donne tout simplement pas beaucoup de précision, à cause des appels asynchrones au garbage collector, etc.Le profilage du tas avec -Xrunhprof ou d'autres outils vous donnera les résultats les plus précis.
Le java.lang.instrument.Instrumentation
La classe fournit un bon moyen d'obtenir la taille d'un objet Java, mais elle nécessite que vous définissiez un premain
et exécutez votre programme avec un agent Java.C'est très ennuyeux lorsque vous n'avez besoin d'aucun agent et que vous devez ensuite fournir un agent Jar factice à votre application.
J'ai donc eu une solution alternative en utilisant le Unsafe
classe de la sun.misc
.Ainsi, en considérant l'alignement du tas des objets en fonction de l'architecture du processeur et en calculant le décalage de champ maximum, vous pouvez mesurer la taille d'un objet Java.Dans l'exemple ci-dessous, j'utilise une classe auxiliaire UtilUnsafe
pour obtenir une référence à sun.misc.Unsafe
objet.
private static final int NR_BITS = Integer.valueOf(System.getProperty("sun.arch.data.model"));
private static final int BYTE = 8;
private static final int WORD = NR_BITS/BYTE;
private static final int MIN_SIZE = 16;
public static int sizeOf(Class src){
//
// Get the instance fields of src class
//
List<Field> instanceFields = new LinkedList<Field>();
do{
if(src == Object.class) return MIN_SIZE;
for (Field f : src.getDeclaredFields()) {
if((f.getModifiers() & Modifier.STATIC) == 0){
instanceFields.add(f);
}
}
src = src.getSuperclass();
}while(instanceFields.isEmpty());
//
// Get the field with the maximum offset
//
long maxOffset = 0;
for (Field f : instanceFields) {
long offset = UtilUnsafe.UNSAFE.objectFieldOffset(f);
if(offset > maxOffset) maxOffset = offset;
}
return (((int)maxOffset/WORD) + 1)*WORD;
}
class UtilUnsafe {
public static final sun.misc.Unsafe UNSAFE;
static {
Object theUnsafe = null;
Exception exception = null;
try {
Class<?> uc = Class.forName("sun.misc.Unsafe");
Field f = uc.getDeclaredField("theUnsafe");
f.setAccessible(true);
theUnsafe = f.get(uc);
} catch (Exception e) { exception = e; }
UNSAFE = (sun.misc.Unsafe) theUnsafe;
if (UNSAFE == null) throw new Error("Could not obtain access to sun.misc.Unsafe", exception);
}
private UtilUnsafe() { }
}
Il y a aussi le Mesureur de mémoire outil (anciennement chez Code Google, maintenant GitHub), qui est simple et publié sous le nom commercial Licence Apache 2.0, comme discuté dans un question similaire.
Cela nécessite également un argument de ligne de commande pour l'interpréteur Java si vous souhaitez mesurer la consommation d'octets de mémoire, mais sinon cela semble très bien fonctionner, du moins dans les scénarios que je l'ai utilisés.
Voici un utilitaire que j'ai créé en utilisant certains des exemples liés pour gérer 32 bits, 64 bits et 64 bits avec une POO compressée.Il utilise sun.misc.Unsafe
.
Il utilise Unsafe.addressSize()
pour obtenir la taille d'un pointeur natif et Unsafe.arrayIndexScale( Object[].class )
pour la taille d'une référence Java.
Il utilise le décalage de champ d'une classe connue pour déterminer la taille de base d'un objet.
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.IdentityHashMap;
import java.util.Stack;
import sun.misc.Unsafe;
/** Usage:
* MemoryUtil.sizeOf( object )
* MemoryUtil.deepSizeOf( object )
* MemoryUtil.ADDRESS_MODE
*/
public class MemoryUtil
{
private MemoryUtil()
{
}
public static enum AddressMode
{
/** Unknown address mode. Size calculations may be unreliable. */
UNKNOWN,
/** 32-bit address mode using 32-bit references. */
MEM_32BIT,
/** 64-bit address mode using 64-bit references. */
MEM_64BIT,
/** 64-bit address mode using 32-bit compressed references. */
MEM_64BIT_COMPRESSED_OOPS
}
/** The detected runtime address mode. */
public static final AddressMode ADDRESS_MODE;
private static final Unsafe UNSAFE;
private static final long ADDRESS_SIZE; // The size in bytes of a native pointer: 4 for 32 bit, 8 for 64 bit
private static final long REFERENCE_SIZE; // The size of a Java reference: 4 for 32 bit, 4 for 64 bit compressed oops, 8 for 64 bit
private static final long OBJECT_BASE_SIZE; // The minimum size of an Object: 8 for 32 bit, 12 for 64 bit compressed oops, 16 for 64 bit
private static final long OBJECT_ALIGNMENT = 8;
/** Use the offset of a known field to determine the minimum size of an object. */
private static final Object HELPER_OBJECT = new Object() { byte b; };
static
{
try
{
// Use reflection to get a reference to the 'Unsafe' object.
Field f = Unsafe.class.getDeclaredField( "theUnsafe" );
f.setAccessible( true );
UNSAFE = (Unsafe) f.get( null );
OBJECT_BASE_SIZE = UNSAFE.objectFieldOffset( HELPER_OBJECT.getClass().getDeclaredField( "b" ) );
ADDRESS_SIZE = UNSAFE.addressSize();
REFERENCE_SIZE = UNSAFE.arrayIndexScale( Object[].class );
if( ADDRESS_SIZE == 4 )
{
ADDRESS_MODE = AddressMode.MEM_32BIT;
}
else if( ADDRESS_SIZE == 8 && REFERENCE_SIZE == 8 )
{
ADDRESS_MODE = AddressMode.MEM_64BIT;
}
else if( ADDRESS_SIZE == 8 && REFERENCE_SIZE == 4 )
{
ADDRESS_MODE = AddressMode.MEM_64BIT_COMPRESSED_OOPS;
}
else
{
ADDRESS_MODE = AddressMode.UNKNOWN;
}
}
catch( Exception e )
{
throw new Error( e );
}
}
/** Return the size of the object excluding any referenced objects. */
public static long shallowSizeOf( final Object object )
{
Class<?> objectClass = object.getClass();
if( objectClass.isArray() )
{
// Array size is base offset + length * element size
long size = UNSAFE.arrayBaseOffset( objectClass )
+ UNSAFE.arrayIndexScale( objectClass ) * Array.getLength( object );
return padSize( size );
}
else
{
// Object size is the largest field offset padded out to 8 bytes
long size = OBJECT_BASE_SIZE;
do
{
for( Field field : objectClass.getDeclaredFields() )
{
if( (field.getModifiers() & Modifier.STATIC) == 0 )
{
long offset = UNSAFE.objectFieldOffset( field );
if( offset >= size )
{
size = offset + 1; // Field size is between 1 and PAD_SIZE bytes. Padding will round up to padding size.
}
}
}
objectClass = objectClass.getSuperclass();
}
while( objectClass != null );
return padSize( size );
}
}
private static final long padSize( final long size )
{
return (size + (OBJECT_ALIGNMENT - 1)) & ~(OBJECT_ALIGNMENT - 1);
}
/** Return the size of the object including any referenced objects. */
public static long deepSizeOf( final Object object )
{
IdentityHashMap<Object,Object> visited = new IdentityHashMap<Object,Object>();
Stack<Object> stack = new Stack<Object>();
if( object != null ) stack.push( object );
long size = 0;
while( !stack.isEmpty() )
{
size += internalSizeOf( stack.pop(), stack, visited );
}
return size;
}
private static long internalSizeOf( final Object object, final Stack<Object> stack, final IdentityHashMap<Object,Object> visited )
{
// Scan for object references and add to stack
Class<?> c = object.getClass();
if( c.isArray() && !c.getComponentType().isPrimitive() )
{
// Add unseen array elements to stack
for( int i = Array.getLength( object ) - 1; i >= 0; i-- )
{
Object val = Array.get( object, i );
if( val != null && visited.put( val, val ) == null )
{
stack.add( val );
}
}
}
else
{
// Add unseen object references to the stack
for( ; c != null; c = c.getSuperclass() )
{
for( Field field : c.getDeclaredFields() )
{
if( (field.getModifiers() & Modifier.STATIC) == 0
&& !field.getType().isPrimitive() )
{
field.setAccessible( true );
try
{
Object val = field.get( object );
if( val != null && visited.put( val, val ) == null )
{
stack.add( val );
}
}
catch( IllegalArgumentException e )
{
throw new RuntimeException( e );
}
catch( IllegalAccessException e )
{
throw new RuntimeException( e );
}
}
}
}
}
return shallowSizeOf( object );
}
}
Sans avoir à vous soucier de l'instrumentation, etc., et si vous n'avez pas besoin de connaître la taille exacte en octets d'un objet, vous pouvez utiliser l'approche suivante :
System.gc();
Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
do your job here
System.gc();
Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
De cette façon, vous lisez la mémoire utilisée avant et après, et en appelant le GC juste avant d'obtenir la mémoire utilisée, vous réduisez le "bruit" presque à 0.
Pour un résultat plus fiable, vous pouvez exécuter votre travail n fois, puis diviser la mémoire utilisée par n, obtenant ainsi la quantité de mémoire nécessaire à une exécution.De plus, vous pouvez tout exécuter plusieurs fois et faire une moyenne.
Il n’y a pas d’appel de méthode, si c’est ce que vous demandez.Avec un peu de recherche, je suppose que vous pourriez écrire le vôtre.Une instance particulière a une taille fixe dérivée du nombre de références et de valeurs primitives ainsi que des données de comptabilité de l'instance.Vous parcourriez simplement le graphe d’objets.Moins les types de lignes sont variés, plus c'est facile.
Si c'est trop lent ou si cela pose simplement plus de problèmes que cela n'en vaut la peine, il existe toujours une bonne vieille règle empirique de comptage de lignes.
J'ai écrit une fois un test rapide pour estimer à la volée :
public class Test1 {
// non-static nested
class Nested { }
// static nested
static class StaticNested { }
static long getFreeMemory () {
// waits for free memory measurement to stabilize
long init = Runtime.getRuntime().freeMemory(), init2;
int count = 0;
do {
System.out.println("waiting..." + init);
System.gc();
try { Thread.sleep(250); } catch (Exception x) { }
init2 = init;
init = Runtime.getRuntime().freeMemory();
if (init == init2) ++ count; else count = 0;
} while (count < 5);
System.out.println("ok..." + init);
return init;
}
Test1 () throws InterruptedException {
Object[] s = new Object[10000];
Object[] n = new Object[10000];
Object[] t = new Object[10000];
long init = getFreeMemory();
//for (int j = 0; j < 10000; ++ j)
// s[j] = new Separate();
long afters = getFreeMemory();
for (int j = 0; j < 10000; ++ j)
n[j] = new Nested();
long aftersn = getFreeMemory();
for (int j = 0; j < 10000; ++ j)
t[j] = new StaticNested();
long aftersnt = getFreeMemory();
System.out.println("separate: " + -(afters - init) + " each=" + -(afters - init) / 10000);
System.out.println("nested: " + -(aftersn - afters) + " each=" + -(aftersn - afters) / 10000);
System.out.println("static nested: " + -(aftersnt - aftersn) + " each=" + -(aftersnt - aftersn) / 10000);
}
public static void main (String[] args) throws InterruptedException {
new Test1();
}
}
Le concept général est d'allouer des objets et de mesurer les changements dans l'espace libre du tas.La clé étant getFreeMemory()
, lequel demande l'exécution du GC et attend que la taille du tas libre signalée se stabilise.Le résultat de ce qui précède est :
nested: 160000 each=16
static nested: 160000 each=16
C'est ce à quoi nous nous attendons, compte tenu du comportement d'alignement et de l'éventuelle surcharge d'en-tête de bloc de tas.
La méthode d'instrumentation détaillée dans la réponse acceptée est ici la plus précise.La méthode que j'ai décrite est précise mais uniquement dans des conditions contrôlées où aucun autre thread ne crée/supprime des objets.
Utilisez simplement Java Visual VM.
Il contient tout ce dont vous avez besoin pour profiler et déboguer les problèmes de mémoire.
Il dispose également d'une console OQL (Object Query Language) qui vous permet de faire de nombreuses choses utiles, dont l'une étant sizeof(o)
Ma réponse est basée sur le code fourni par Nick.Ce code mesure la quantité totale d'octets occupés par l'objet sérialisé.Donc, cela mesure en fait les éléments de sérialisation + l'empreinte mémoire des objets simples (il suffit de sérialiser par exemple int
et vous verrez que la quantité totale d'octets sérialisés n'est pas 4
).Donc, si vous souhaitez que le numéro d'octet brut soit utilisé exactement pour votre objet, vous devez modifier un peu ce code.Ainsi:
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class ObjectSizeCalculator {
private Object getFirstObjectReference(Object o) {
String objectType = o.getClass().getTypeName();
if (objectType.substring(objectType.length()-2).equals("[]")) {
try {
if (objectType.equals("java.lang.Object[]"))
return ((Object[])o)[0];
else if (objectType.equals("int[]"))
return ((int[])o)[0];
else
throw new RuntimeException("Not Implemented !");
} catch (IndexOutOfBoundsException e) {
return null;
}
}
return o;
}
public int getObjectSizeInBytes(Object o) {
final String STRING_JAVA_TYPE_NAME = "java.lang.String";
if (o == null)
return 0;
String objectType = o.getClass().getTypeName();
boolean isArray = objectType.substring(objectType.length()-2).equals("[]");
Object objRef = getFirstObjectReference(o);
if (objRef != null && !(objRef instanceof Serializable))
throw new RuntimeException("Object must be serializable for measuring it's memory footprint using this method !");
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(o);
oos.close();
byte[] bytes = baos.toByteArray();
for (int i = bytes.length - 1, j = 0; i != 0; i--, j++) {
if (objectType != STRING_JAVA_TYPE_NAME) {
if (bytes[i] == 112)
if (isArray)
return j - 4;
else
return j;
} else {
if (bytes[i] == 0)
return j - 1;
}
}
} catch (Exception e) {
return -1;
}
return -1;
}
}
J'ai testé cette solution avec des types primitifs, String et certaines classes triviales.Il se peut également qu’il y ait des cas non couverts.
MISE À JOUR: Exemple modifié pour prendre en charge le calcul de l'empreinte mémoire des objets du tableau.
Vous pouvez générer un vidage de tas (avec jmap, par exemple), puis analyser la sortie pour trouver la taille des objets.Il s'agit d'une solution hors ligne, mais vous pouvez examiner les tailles peu profondes et profondes, etc.
long heapSizeBefore = Runtime.getRuntime().totalMemory();
// Code for object construction
...
long heapSizeAfter = Runtime.getRuntime().totalMemory();
long size = heapSizeAfter - heapSizeBefore;
size vous donne l'augmentation de l'utilisation de la mémoire du jvm en raison de la création d'objets et il s'agit généralement de la taille de l'objet.
Cette réponse n'est pas liée à la taille de l'objet, mais lorsque vous utilisez un tableau pour accueillir les objets ;la taille de mémoire qu'il allouera à l'objet.
Ainsi, les tableaux, listes ou cartes de toutes ces collections ne stockeront pas vraiment les objets (uniquement au moment des primitives, la taille réelle de la mémoire des objets est nécessaire), elles stockeront uniquement les références pour ces objets.
Maintenant le Used heap memory = sizeOfObj + sizeOfRef (* 4 bytes) in collection
- (4/8 octets) dépend du système d'exploitation (32/64 bits)
PRIMITIVES
int [] intArray = new int [1]; will require 4 bytes.
long [] longArray = new long [1]; will require 8 bytes.
OBJETS
Object[] objectArray = new Object[1]; will require 4 bytes. The object can be any user defined Object.
Long [] longArray = new Long [1]; will require 4 bytes.
Je veux dire que tout l'objet REFERENCE n'a besoin que de 4 octets de mémoire.Il peut s'agir d'une référence de chaîne OU d'une référence d'objet double, mais cela dépend de la création de l'objet, la mémoire nécessaire variera.
par exemple) Si je crée un objet pour la classe ci-dessous ReferenceMemoryTest
alors 4 + 4 + 4 = 12 octets de mémoire seront créés.La mémoire peut différer lorsque vous essayez d'initialiser les références.
class ReferenceMemoryTest {
public String refStr;
public Object refObj;
public Double refDoub;
}
Ainsi, lors de la création d’un tableau d’objets/références, tout son contenu sera occupé par des références NULL.Et nous savons que chaque référence nécessite 4 octets.
Et enfin, l'allocation de mémoire pour le code ci-dessous est de 20 octets.
ReferenceMemoryTest ref1 = new ReferenceMemoryTest();(4 (REF1) + 12 = 16 octets) RefeCoMoryTest REF2 = REF1;( 4(réf2) + 16 = 20 octets)
Supposons que je déclare une classe nommée Complex
comme:
public class Complex {
private final long real;
private final long imaginary;
// omitted
}
Afin de voir combien de mémoire est allouée aux instances actives de cette classe :
$ jmap -histo:live <pid> | grep Complex
num #instances #bytes class name (module)
-------------------------------------------------------
327: 1 32 Complex
Pour JSONObject, le code ci-dessous peut vous aider.
`JSONObject.toString().getBytes("UTF-8").length`
renvoie la taille en octets
Je l'ai vérifié avec mon objet JSONArray en l'écrivant dans un fichier.Cela donne la taille de l'objet.
Je doute que vous souhaitiez le faire par programme, à moins que vous ne souhaitiez le faire une fois et le stocker pour une utilisation future.C'est une chose coûteuse à faire.Il n'y a pas d'opérateur sizeof() en Java, et même s'il y en avait, il ne compterait que le coût des références à d'autres objets et la taille des primitives.
Une façon de procéder consiste à sérialiser l'élément dans un fichier et à examiner la taille du fichier, comme ceci :
Serializable myObject;
ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("obj.ser"));
oos.write (myObject);
oos.close ();
Bien entendu, cela suppose que chaque objet est distinct et ne contient aucune référence non transitoire à autre chose.
Une autre stratégie consisterait à prendre chaque objet et à examiner ses membres par réflexion et à additionner les tailles (boolean & byte = 1 octet, short & char = 2 octets, etc.), en descendant dans la hiérarchie des membres.Mais c'est fastidieux et coûteux et finit par faire la même chose que la stratégie de sérialisation.