Heim  >  Artikel  >  Java  >  Eine kurze Analyse der Java-Objektgröße

Eine kurze Analyse der Java-Objektgröße

黄舟
黄舟Original
2017-03-15 11:35:571603Durchsuche


Vor kurzem hatte ich eine Laune und interessierte mich plötzlich für die Speichergröße von Java-Objekten. Ich habe einige Informationen im Internet gesammelt und hoffe, dass Sie mir helfen können .
Wenn: Sie die Speichergröße berechnen können, die vom neuen String-Objekt („abc“) in der JVM belegt wird (komprimierte Größe 48 B, unkomprimierte Größe 64 B in 64-Bit-JDK7), dann können Sie es hier beenden ~


Speicherlayout von Java-Objekten: Objektheader (Header), Instanzdaten (Instance Data) und Ausrichtungsauffüllung (Padding) .
Der Objektheader der virtuellen Maschine enthält zwei Informationsteile. Der erste Teil wird verwendet, um die Laufzeitdaten des Objekts selbst zu speichern , z. B. HashCode, GC-Generierungsalter, Sperrstatusflag, Sperre vom Thread gehalten, und Bias-Thread-ID, Bias-Zeitstempel usw. Die Länge dieses Teils der Daten beträgt 4 B bzw. 8 B in virtuellen 32-Bit- und 64-Bit-Maschinen (die Zeigerkomprimierung ist nicht aktiviert). Er wird offiziell „Mark Word“ genannt.
Ein weiterer Teil des Objekts ist der Typzeiger (klass) , der der Zeiger des Objekts auf seine Klassenmetadaten ist. Die virtuelle Maschine verwendet diesen Zeiger, um zu bestimmen, zu welcher Klasse das Objekt gehört . Wenn es sich bei dem Objekt um ein Java-Array handelt, muss außerdem ein Datenelement im Objektheader vorhanden sein, um die Länge des Arrays aufzuzeichnen, da die virtuelle Maschine die Größe des Java-Objekts anhand der Metadateninformationen gewöhnlicher Java-Objekte bestimmen kann , kann aber aus den Metadaten des Arrays die Größe des Arrays nicht bestimmen.
Der Objektheader belegt 8B auf 32-Bit-Systemen und 16B auf 64-Bit-Systemen. Ob es sich um ein 32-Bit-System oder ein 64-Bit-System handelt, Objekte sind 8-Byte-ausgerichtet. Wenn Java die Zeigerkomprimierung im 64-Bit-Modus aktiviert, ist der Header 4B größer als im 32-Bit-Modus (der Markierungsbereich wird um 8B verschoben und der kclass-Bereich wird komprimiert). Der Header wird 8B größer sein (sowohl Mark als auch Kclass sind 8B groß). 0<=padding<8. Die folgenden Anweisungen basieren auf HotSpot.

Wie in Referenz 2 erwähnt, bietet die nach JDK5 bereitgestellte java.lang.instrument.Instrumentation eine umfangreiche API zum Verfolgen verschiedener Aspekte der Struktur und Messen der Objektgröße. Für die Verwendung dieses Dings ist jedoch die Verwendung eines Java-Agenten erforderlich. Ich werde sie hier nicht erläutern, sondern nur deren Verwendung.
Diese Klasse wird in Referenz 3 bereitgestellt. Ich persönlich finde sie sehr praktisch. Der Code ist in Anhang 1 unten dargestellt (der Code ist relativ lang, daher werde ich ihn einfach am Ende des Artikels einfügen):

Dieser Code kann direkt kopiert und dann in ein JAR-Paket (mit dem Namen agent.jar) gepackt werden. Wenn es nicht erfolgreich gepackt wurde, können Sie das vom Blogger gepackte Paket direkt herunterladen. Achten Sie darauf, eine Zeile in META hinzuzufügen. INF/MANIFEST.MF:

Der Code lautet beispielsweise wie folgt (das System des Bloggers ist 64-Bit und verwendet 64-Bit-JDK7):
Premain-Class: com.zzh.size.MySizeOf (注意":"后面的空格,否则会报错:invalid header field.)

Als nächstes Kompilieren und ausführen, die Schritte sind wie folgt:
import com.zzh.size.MySizeOf;public class ObjectSize
{    public static void  main(String args[])
    {
        System.out.println(MySizeOf.sizeOf(new Object()));
    }
}

    Kompilieren (agent.jar wird im aktuellen Verzeichnis abgelegt): javac -classpath agent.jar ObjectSize.java
  1. Ausführen: java -javaagent:agent.jar ObjectSize (Ausgabeergebnis: 16. Was die Analyse dieses Ergebnisses betrifft, werden wir später näher darauf eingehen)
  2. JDK6 führt den Parameter -XX:+UseCompressedOops ein. Dieser Parameter wird im 32G-Speicher standardmäßig automatisch aktiviert. Die Zeigerkomprimierung kann durch Hinzufügen von -XX:-UseCompressedOops zu den Laufparametern deaktiviert werden.
Verwenden Sie die Instrumentierung, um die Größe eines Objekts anschaulicher auszudrücken. Tatsächlich kann die Größe eines Objekts manuell berechnet werden, um die Rationalität und Richtigkeit zu beweisen Aufgrund des theoretischen Wissens spiegelt sich der spezifische Algorithmus im folgenden Codefall wider.


Ergänzung: Die Speichernutzung des primitiven Typs ist wie folgt:

 引用类型在32位系统上每个占用4B, 在64位系统上每个占用8B。


案例分析
 扯了这么多犊子,估计看的玄乎玄乎的,来几段代码案例来实践一下。

案例1:上面的new Object()的大小为16B,这里再重申一下,博主测试机是64位的JDK7,如无特殊说明,默认开启指针压缩。

new Object()的大小=对象头12B(8Bmak区,4Bkclass区)+padding的4B=16B

案例2

    static class A{        int a;
    }    static class B{        int a;        int b;
    }    public static void  main(String args[])
    {
        System.out.println(MySizeOf.sizeOf(new Integer(1)));
        System.out.println(MySizeOf.sizeOf(new A()));
        System.out.println(MySizeOf.sizeOf(new B()));
    }

输出结果:

(指针压缩) 16    16    24
(指针未压缩)24    24    24

分析1(指针压缩):

new Integer(1)的大小=12B对象头+4B的实例数据+0B的填充=16Bnew 
A()的大小=12B对象头+4B的实例数据+0B的填充=16B
new B()的大小=12B对象头+2*4B的实例数据=20B,填充之后=24B

分析2(指针未压缩):

new Integer(1)的大小=16B对象头+4B的实例数据+4B的填充=24B
new A()的大小=16B对象头+4B的实例数据+4B的填充=24B
new B()的大小=16B对象头+2*4B的实例数据+0B的填充=24B

案例3

System.out.println(MySizeOf.sizeOf(new int[2]));
System.out.println(MySizeOf.sizeOf(new int[3]));
System.out.println(MySizeOf.sizeOf(new char[2]));
System.out.println(MySizeOf.sizeOf(new char[3]));

输出结果:

(指针压缩) 24    32    24    24
(指针未压缩) 32    40    32    32

分析1(指针压缩):

new int[2]的大小=12B对象头+压缩情况下数组比普通对象多4B来存放长度+2*4B的int实例大小=24B
new int[3]的大小=12B对象头+4B长度+3*4B的int实例大小=28B,填充4B =32B
new char[2]的大小=12B对象头+4B长度+2*2B的实例大小=20B,填充4B=24B
new char[3]的大小=12B对象头+4B长度+3*2B的实例大小+2B填充=24B
(PS:new char[5]的大小=32B)

分析2(指针未压缩):

new int[2]的大小=16B对象头+未压缩情况下数组比普通对象多8B来存放长度+2*4B实例大小=32B
new int[3]的大小=16B+8B+3*4B+4B填充=40B
new char[2]的大小=16B+8B+2*2B+4B填充=32B
new char[2]的大小=16B+8B+3*2B+2B填充=32B
(PS:new char[5]的大小为40B)

案例4(sizeOf只计算本体对象大小,fullSizeOf计算本体对象大小和引用的大小,具体可以翻阅附录1的代码).

System.out.println(MySizeOf.sizeOf(new String("a")));
System.out.println(MySizeOf.fullSizeOf(new String("a")));
System.out.println(MySizeOf.fullSizeOf(new String("aaaaa")));

输出结果:

(指针压缩)24    48    56    
(指针未压缩)32    64   72

分析1(指针压缩):

翻看String(JDK7)的源码可以知道,
String有这几个成员变量:(static变量属于类,不属于实例,所以声明为static的不计入对象的大小)
private final char value[];
private int hash;
private transient int hash32 = 0;

MySizeOf.sizeOf(new String("a"))的大小=12B对象头+2*4B(成员变量hash和hash32)+4B(压缩的value指针)=24B
MySizeOf.fullSizeOf(new 
String("a"))的大小=12B对象头+2*4B(成员变量hash和hash32)+4B指针+
(value数组的大小=12B对象头+4B数组长度+1*2B实例大小+6B填充=24B)=12B+8B+4B+24B=48B
(PS: new String("aa"),new String("aaa"),new String("aaaa")的fullSizeOf大小都为48B)
MySizeOf.fullSizeOf(new String("aaaaa"))的大小=12B+2*4B+4B+(12B+4B+5*2B+6B填充)=24B+32B=56B

分析2(指针未压缩)

MySizeOf.sizeOf(new String("a"))的大小=16B+2*4B+8B(位压缩的指针大小) =32B
MySizeOf.fullSizeOf(new String("a"))的大小=16B对象头+2*4B(成员变量hash和hash32)+8B指针+(value数组的大小=16B对象头+8B数组长度+1*2B实例大小+6B填充=32B)=32B+32B=64B
(PS: new String("aa"),new String("aaa"),new String("aaaa")的fullSizeOf大小都为64B)
MySizeOf.fullSizeOf(new String("aaaaa"))的大小=16B+2*4B+8B+(16B+8B+5*2B+6B填充)=32B+40B=72B

 这些计算结果只会少不会多,因为在代码运行过程中,一些对象的头部会伸展,mark区域会引用一些外部的空间(轻量级锁,偏向锁,这里不展开),所以官方给出的说明也是,最少会占用多少字节,绝对不会说只占用多少字节。

如果是32位的JDK,可以算一下或者运行一下上面各个案例的结果。

 看来上面的这些我们来手动计算下new String()的大小:
1. 指针压缩的情况

12B对象头+2*4B实例变量+4B指针+(12B对象头+4B数组长度大小+0B实例大小)=24B+16B=40B
  1. 指针未压缩的情况

16B+2*4B+8B指针+(16B+8B数组长度大小+0B)=32B+24B=56B

 所以一个空的String对象最少也要占用40B的大小,所以大家在以后应该编码过程中要稍微注意下。其实也并不要太在意,相信能从文章开头看到这里的同学敲的代码也数以万计了,不在意这些也并没有什么不妥之处,只不过如果如果你了解了的话对于提升自己的逼格以及代码优化水平有很大的帮助,比如:能用基本类型的最好别用其包装类。

附:agent.jar包源码

package com.zzh.size;import java.lang.instrument.Instrumentation;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashSet;
import java.util.Set;
public class MySizeOf{
     static Instrumentation inst;  

        public static void premain(String args, Instrumentation instP) {  
            inst = instP;  
        }  

        /** 
         * 直接计算当前对象占用空间大小,包括当前类及超类的基本类型实例字段大小、<br></br> 
         * 引用类型实例字段引用大小、实例基本类型数组总占用空间、实例引用类型数组引用本身占用空间大小;<br></br> 
         * 但是不包括超类继承下来的和当前类声明的实例引用字段的对象本身的大小、实例引用数组引用的对象本身的大小 <br></br> 
         * 
         * @param obj 
         * @return 
         */  
        public static long sizeOf(Object obj) {  
            return inst.getObjectSize(obj);  
        }  

        /** 
         * 递归计算当前对象占用空间总大小,包括当前类和超类的实例字段大小以及实例字段引用对象大小 
         * 
         * @param objP 
         * @return 
         * @throws IllegalAccessException 
         */  
        public static long fullSizeOf(Object objP) throws IllegalAccessException {  
            Set<Object> visited = new HashSet<Object>();  
            Deque<Object> toBeQueue = new ArrayDeque<>();  
            toBeQueue.add(objP);  
            long size = 0L;  
            while (toBeQueue.size() > 0) {  
                Object obj = toBeQueue.poll();  
                //sizeOf的时候已经计基本类型和引用的长度,包括数组  
                size += skipObject(visited, obj) ? 0L : sizeOf(obj);  
                Class<?> tmpObjClass = obj.getClass();  
                if (tmpObjClass.isArray()) {  
                    //[I , [F 基本类型名字长度是2  
                    if (tmpObjClass.getName().length() > 2) {  
  
  for (int i = 0, len = Array.getLength(obj); i < len; i++) {  
                            Object tmp = Array.get(obj, i);  
                            if (tmp != null) {  
                                //非基本类型需要深度遍历其对象  
                                toBeQueue.add(Array.get(obj, i));  
                            }  
                        }  
                    }  
                } else {  
                    while (tmpObjClass != null) {  
                        Field[] fields = tmpObjClass.getDeclaredFields();  
                        for (Field field : fields) {  
      
       if (Modifier.isStatic(field.getModifiers())   //静态不计  
                                    || field.getType().isPrimitive()) {    //基本类型不重复计  
                                continue;  
                            }  

                            field.setAccessible(true);  
                            Object fieldValue = field.get(obj);  
                            if (fieldValue == null) {  
                                continue;  
                            }  
                            toBeQueue.add(fieldValue);  
                        }  
                        tmpObjClass = tmpObjClass.getSuperclass();  
                    }  
                }  
            }  
            return size;  
        }  

        /** 
         * String.intern的对象不计;计算过的不计,也避免死循环 
         * 
         * @param visited 
         * @param obj 
         * @return 
         */  
        static boolean skipObject(Set<Object> visited, Object obj) {  
            if (obj instanceof String && obj == ((String) obj).intern()) {  
                return true;  
            }  
            return visited.contains(obj);  
        }  
}

Das obige ist der detaillierte Inhalt vonEine kurze Analyse der Java-Objektgröße. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn