Heim  >  Artikel  >  Java  >  Wie viel Speicher belegt ein Java-Objekt?

Wie viel Speicher belegt ein Java-Objekt?

伊谢尔伦
伊谢尔伦Original
2016-11-26 11:45:441582Durchsuche

Kürzlich habe ich „Ausführliches Verständnis der Java Virtual Machine“ gelesen und ein besseres Verständnis für das Speicherlayout von Java-Objekten gewonnen. Daher kam mir natürlich eine sehr häufige Frage in den Sinn: Wie viel Speicher benötigt ein Java? Objekt belegen?

Ich habe im Internet einen Blog gefunden, der sehr gut spricht: http://yueyemaitian.iteye.com/blog/2033046 Der darin angebotene Kurs ist auch sehr praktisch:

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; 
   
/**
 * 对象占用字节大小工具类
 *
 * @author tianmai.fh
 * @date 2014-03-18 11:29
 */ 
public class SizeOfObject { 
    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<Object>(); 
        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); 
    } 
}

Sie können diesen Code verwenden, um ihn zu lesen und zu überprüfen. Beachten Sie, dass zum Ausführen dieses Programms die Instrumentierung über Javaagent eingefügt werden muss. Weitere Informationen finden Sie im Originalblog. Heute fasse ich hauptsächlich die Grundregeln für die manuelle Berechnung der Anzahl der von Java-Objekten belegten Bytes zusammen. Als grundlegende Fähigkeit ist get√ erforderlich, um Java-Neulingen wie mir zu helfen.

Lassen Sie uns vor der Einführung kurz das Speicherlayout von Java-Objekten überprüfen: Objektheader (Header), Instanzdaten (Instanzdaten) und Ausrichtungsauffüllung (Padding). Weitere Informationen finden Sie in meiner Lektüre Notizen. Außerdem: Die Ergebnisse können in verschiedenen Umgebungen unterschiedlich sein. Meine Umgebung ist eine virtuelle HotSpot-Maschine und 64-Bit-Windows.

Geben Sie nun den Text ein:

Objektheader

Der Objektheader belegt 8 Byte auf 32-Bit-Systemen und 16 Byte auf 64-Bit-Systemen.

Wie viel Speicher belegt ein Java-Objekt?

Instanzdaten

Die Speichernutzung des primitiven Typs ist wie folgt:

Erforderlicher Speicher des primitiven Typs (Bytes)

boolean 1  

byte 1  

short 2  

char 2  

int 4  

float 4

long 8

double 8

Der Referenztyp belegt auf 32-Bit-Systemen jeweils 4 Bytes und auf 64-Bit-Systemen jeweils 8 Bytes.

Ausrichtungsauffüllung

Die Ausrichtung von HotSpot ist eine 8-Byte-Ausrichtung:

(Objektkopf + Instanzdaten + Auffüllung) % 8 entspricht 0 und 0

  Zeigerkomprimierung

  Die vom Objekt belegte Speichergröße wird durch den VM-Parameter UseCompressedOops beeinflusst.

 1) Auswirkungen auf den Objekt-Header

Aktivieren Sie (-XX:+UseCompressedOops). Die Größe des Objekt-Headers beträgt 12 Byte (64-Bit-Maschine).

static class A {
        int a;
    }

Vom Objekt A belegter Speicher:

Zeigerkomprimierung deaktivieren: 16+4=20 ist kein Vielfaches von 8, also +padding/4= 24

Wie viel Speicher belegt ein Java-Objekt?

Zeigerkomprimierung aktivieren: 12+4=16 ist bereits ein Vielfaches von 8, es ist kein Auffüllen erforderlich.

Wie viel Speicher belegt ein Java-Objekt?

2) Auswirkungen auf den Referenztyp

Auf einer 64-Bit-Maschine belegt der Referenztyp 8 Bytes und nach dem Drehen Bei der Zeigerkomprimierung werden 4 Bytes belegt.

static class B2 {
        int b2a;
        Integer b2b;
}

B2-Objektspeichernutzung:

Zeigerkomprimierung deaktivieren: 16+4+8=28 ist kein Vielfaches von 8, also +padding/4 =32

Wie viel Speicher belegt ein Java-Objekt?

Zeigerkomprimierung aktivieren: 12+4+4=20 ist kein Vielfaches von 8, also +padding/4=24

Wie viel Speicher belegt ein Java-Objekt?

Array-Objekt

Auf einem 64-Bit-Computer belegt der Objektheader des Array-Objekts 24 Byte und nach Aktivierung der Komprimierung 16 Byte. Der Grund, warum es mehr Speicher beansprucht als gewöhnliche Objekte, liegt darin, dass zusätzlicher Speicherplatz zum Speichern der Länge des Arrays benötigt wird.

Berücksichtigen Sie zunächst die von der neuen Ganzzahl[0] belegte Speichergröße. Die Länge beträgt 0, was der Größe des Objektheaders entspricht:

Komprimierung ist nicht aktiviert: 24 Bytes

Wie viel Speicher belegt ein Java-Objekt?

Nach dem Einschalten der Komprimierung: 16 Bytes

Wie viel Speicher belegt ein Java-Objekt?

Berechnen Sie dann die neue Ganzzahl[ 1], new Integer[2 ], new Integer[3] und new Integer[4] sind einfach:

Ohne Komprimierung:

Wie viel Speicher belegt ein Java-Objekt?

  开启压缩:

Wie viel Speicher belegt ein Java-Objekt?

  拿new Integer[3]来具体解释下:

  未开启压缩:24(对象头)+8*3=48,不需要padding;

  开启压缩:16(对象头)+3*4=28,+padding/4=32,其他依次类推。

  自定义类的数组也是一样的,比如:

static class B3 {
        int a;
        Integer b;
    }

    new B3[3]占用的内存大小:

  未开启压缩:48

  开启压缩后:32

  复合对象

  计算复合对象占用内存的大小其实就是运用上面几条规则,只是麻烦点。

  1)对象本身的大小

  直接计算当前对象占用空间大小,包括当前类及超类的基本类型实例字段大小、引用类型实例字段引用大小、实例基本类型数组总占用空间、实例引用类型数组引用本身占用空间大小; 但是不包括超类继承下来的和当前类声明的实例引用字段的对象本身的大小、实例引用数组引用的对象本身的大小。

static class B {
        int a;
        int b;
    }
static class C {
        int ba;
        B[] as = new B[3];
 
        C() {
            for (int i = 0; i < as.length; i++) {
                as[i] = new B();
            }
        }
    }

    未开启压缩:16(对象头)+4(ba)+8(as引用的大小)+padding/4=32

  开启压缩:12+4+4+padding/4=24

  2)当前对象占用的空间总大小

  递归计算当前对象占用空间总大小,包括当前类和超类的实例字段大小以及实例字段引用对象大小。

  递归计算复合对象占用的内存的时候需要注意的是:对齐填充是以每个对象为单位进行的,看下面这个图就很容易明白。

Wie viel Speicher belegt ein Java-Objekt?

现在我们来手动计算下C对象占用的全部内存是多少,主要是三部分构成:C对象本身的大小+数组对象的大小+B对象的大小。

  未开启压缩:

  (16 + 4 + 8+4(padding)) + (24+ 8*3) +(16+8)*3 = 152bytes

  开启压缩:

  (12 + 4 + 4 +4(padding)) + (16 + 4*3 +4(数组对象padding)) + (12+8+4(B对象padding))*3= 128bytes

  大家有兴趣的可以试试。

  实际工作中真正需要手动计算对象大小的场景应该很少,但是个人觉得做为基础知识每个Java开发人员都应该了解,另外:对自己写的代码大概占用多少内存,内存中是怎么布局的应该有一个直觉性的认识。


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