>  기사  >  Java  >  Java 객체 크기에 대한 간략한 분석

Java 객체 크기에 대한 간략한 분석

黄舟
黄舟원래의
2017-03-15 11:35:571604검색


최근에 갑자기 Java 객체의 메모리 크기에 관심이 생겼습니다. 인터넷에서 정보를 수집하고 정리하는 데 도움이 되었으면 좋겠습니다. .
JVM에서 새 문자열("abc") 객체가 차지하는 메모리 크기를 계산할 수 있는 경우(64비트 JDK7에서는 압축 크기 48B, 압축되지 않은 크기 64B) 여기서 끝낼 수 있습니다~


Java 객체의 메모리 레이아웃: 객체 헤더(Header), 인스턴스 데이터(Instance Data) 및 정렬 패딩(Padding).
가상 머신의 객체 헤더에는 두 부분의 정보가 포함됩니다. 첫 번째 부분은 hashCode, GC 생성 기간, 잠금 상태 플래그, 잠금과 같은 객체 자체의 런타임 데이터를 저장하는 데 사용됩니다. 스레드 및 바이어스 ID, 바이어스 타임스탬프 등에 의해 유지됩니다. 이 데이터 부분의 길이는 32비트 및 64비트 가상 머신에서 각각 4B 및 8B입니다(포인터 압축이 켜져 있지 않음). 공식적으로는 "Mark Word"라고 합니다.
객체의 또 다른 부분은 유형 포인터(klass) 입니다. 이는 클래스 메타데이터에 대한 객체의 포인터입니다. 가상 머신은 이 포인터를 사용하여 객체가 어떤 클래스의 인스턴스인지 결정합니다. . 또한, 객체가 자바 배열인 경우에는 객체 헤더에 배열의 길이를 기록하는 데이터 조각이 있어야 하는데, 이는 가상 머신이 일반 자바 객체의 메타데이터 정보를 통해 자바 객체의 크기를 판단할 수 있기 때문이다. , 그러나 배열의 메타데이터에서 그러나 배열의 크기를 결정할 수 없습니다.
객체 헤더는 32비트 시스템에서는 8B, 64비트 시스템에서는 16B를 차지합니다. 32비트 시스템이든 64비트 시스템이든 개체는 8바이트로 정렬됩니다. Java가 64비트 모드에서 포인터 압축을 켜면 헤더는 32비트 모드보다 4B 더 커집니다(표시 영역은 8B 이동되고 kclass 영역은 압축됩니다). 헤더는 8B 더 커집니다(mark와 kclass는 모두 8B입니다) , 즉
HotSpot의 정렬은 8바이트 정렬입니다. (객체 헤더 + 인스턴스 데이터 + 패딩)%8은 0과 같습니다. 0


참고문헌 2에서 언급했듯이 JDK5 이후에 제공되는 java.lang.instrument.Instrumentation은 구조의 다양한 측면을 추적하고 객체 크기를 측정하기 위한 풍부한 API를 제공합니다. 하지만 이를 위해서는 Java Agent를 사용해야 합니다. Agent와 Instrumentation에 대해서는 여기서는 사용법만 설명하지 않겠습니다.
이 클래스는 Reference 3에서 제공됩니다. 개인적으로 매우 실용적이라고 생각하는 코드는 아래 부록 1에 나와 있습니다. (코드가 비교적 길어서 간단히 글 마지막에 넣겠습니다.)
이 코드를 직접 복사한 후 jar 패키지(agent.jar라는 이름)로 패키징할 수 있습니다. 패키징에 실패하면 블로거가 패키징한 패키지를 직접 다운로드할 수 있습니다. META-에 한 줄을 추가하는 데 주의하세요. INF/MANIFEST.MF:

Premain-Class: com.zzh.size.MySizeOf (注意":"后面的空格,否则会报错:invalid header field.)

사례를 들어보세요. 코드는 다음과 같습니다(블로거의 시스템은 64비트이고 64비트 JDK7을 사용합니다).

import com.zzh.size.MySizeOf;public class ObjectSize
{    public static void  main(String args[])
    {
        System.out.println(MySizeOf.sizeOf(new Object()));
    }
}

다음으로 컴파일하고 실행 단계는 다음과 같습니다.

  1. 컴파일(agent.jar은 현재 디렉터리에 위치): javac -classpath Agent.jar ObjectSize.java

  2. 실행: java -javaagent:agent.jar ObjectSize (출력 결과: 16, 이 결과에 대한 분석은 나중에 자세히 설명하겠습니다.)

JDK6에서는 -XX:+UseCompressedOops 매개변수는 32G 메모리에서 기본적으로 자동으로 켜집니다. 실행 매개변수에 -XX:-UseCompressedOops를 추가하여 포인터 압축을 끌 수 있습니다.
객체의 크기를 보다 생생하게 표현하기 위해 계측을 사용하여 객체의 크기를 직접 계산할 수 있습니다. 이론적 지식의 경우 특정 알고리즘이 아래 코드 사례에 반영됩니다.

보충: 원시형의 메모리 사용량은 다음과 같습니다.

Primitive Type Memory Required(bytes)
boolean 1
byte 1
short 2
char 2
int 4
float 4
long 8
double 8

 引用类型在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);  
        }  
}

위 내용은 Java 객체 크기에 대한 간략한 분석의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.