作者:threedayman

来源:恒生LIGHT云社区

以下内容基于HotSpot虚拟机进行讲解验证。

理论知识

对象在堆内存中的存储布局可以划分为三个部分:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。

对象头中有两类信息

  • 运行时数据:哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。
  • 类型指针:对象指向它类型元数据的指针,虚拟机通过这个指针来确定该对象是哪个类的实例。

实例数据

  • 对象存储的有效信息,即我们在程序代码中定义的各种类型的字段内容。

对齐填充

  • HotSpot虚拟机的自动内存管理系统要求对象起始地址必须是8字节的整数倍,如果对象占用空间不是8个字节的整数倍,就需要通过对齐填充来补全。

对象占用大小受JVM参数UseCompressedOops的影响,64位操作系统,开启时对象头大小为12bytes,关闭压缩指针是对象头大小位16bytes。

实践检验

通过Instrumentation的getObjectSize方法去测量对象占用大小。具体代码如下

package org.example;

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 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<>();
        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);
    }
}

测试对象大小代码

package org.example;

import static org.example.SizeOfObject.fullSizeOf;
import static org.example.SizeOfObject.sizeOf;

public class SizeOfObjectTest {

    /**
     *  -XX:+UseCompressedOops: Header 12  Padding 4= 16
     *  -XX:-UseCompressedOops: Header 16 = 24
     */
    static class A1 {
    }
    /**
     * -XX:+UseCompressedOops: Header 12 + Instance Data 4 = 16
     * -XX:-UseCompressedOops: Header 16 + Instance Data 4 + Padding 4 = 24
     */
    static class A {
        int a;
    }

    /**
     * -XX:+UseCompressedOops: Header 12 + Instance Data 4 + 4 + padding 4 = 24
     * -XX:-UseCompressedOops: Header 16 + Instance Data 4 + 4 = 24
     */
    static class B {
        int a;
        int b;
    }

    /**
     * -XX:+UseCompressedOops: Header 12 + 4 + 4 + padding/4 = 24
     * -XX:-UseCompressedOops:Header 16 + 8 + 4 + padding/4 = 32
     */
    static class B2 {
        int b2a;
        Integer b2b;
    }

    /**
     * 不考虑对象头:
     * 4 + 4 + 4 * 3 + 3 * sizeOf(B)
     */
    static class C extends A {
        int ba;
        B[] as = new B[3];

        C() {
            for (int i = 0; i < as.length; i++) {
                as[i] = new B();
            }
        }
    }

    static class D extends B {
        int da;
        Integer[] di = new Integer[3];
    }

    /**
     * 会算上A的实例字段
     */
    static class E extends A {
        int ea;
        int eb;
    }

    public static void main(String[] args) throws IllegalAccessException {
        System.out.println("sizeOf(new A1())=" + sizeOf(new A1()));
        System.out.println("sizeOf(new A())=" + sizeOf(new A()));
        System.out.println("sizeOf(new B())=" + sizeOf(new B()));
        System.out.println("sizeOf(new B2())=" + sizeOf(new B2()));
        System.out.println("sizeOf(new B[3])=" + sizeOf(new B[3]));
        System.out.println("sizeOf(new C())=" + sizeOf(new C()));
        System.out.println("fullSizeOf(new C())=" + fullSizeOf(new C()));
        System.out.println("sizeOf(new D())=" + sizeOf(new D()));
        System.out.println("fullSizeOf(new D())=" + fullSizeOf(new D()));
        System.out.println("sizeOf(new int[3])=" + sizeOf(new int[3]));
        System.out.println("sizeOf(new Integer(1)=" + sizeOf(new Integer(1)));
        System.out.println("sizeOf(new Integer[0])=" + sizeOf(new Integer[0]));
        System.out.println("sizeOf(new Integer[1])=" + sizeOf(new Integer[1]));
        System.out.println("sizeOf(new Integer[2])=" + sizeOf(new Integer[2]));
        System.out.println("sizeOf(new Integer[3])=" + sizeOf(new Integer[3]));
        System.out.println("sizeOf(new Integer[4])=" + sizeOf(new Integer[4]));
        System.out.println("sizeOf(new A[3])=" + sizeOf(new A[3]));
        System.out.println("sizeOf(new E())=" + sizeOf(new E()));
    }
}

打包插件中增加以下配置

<plugin>
  <artifactId>maven-jar-plugin</artifactId>
  <version>3.0.2</version>
  <configuration>
    <finalName>test</finalName>
    <archive>
      <manifestEntries>
        <Premain-class>org.example.SizeOfObject</Premain-class>
        <Boot-Class-Path></Boot-Class-Path>
        <Can-Redefine-Classes>false</Can-Redefine-Classes>
        <Main-Class>org.example.SizeOfObjectTest</Main-Class>
      </manifestEntries>
      <addMavenDescriptor>false</addMavenDescriptor>
    </archive>
  </configuration>
</plugin>

配置指定的main方法入口类、agent增强类入口。默认情况下 开启了指针压缩。

D:\ideaproject\size-agent\target>java -javaagent:test.jar -jar test.jar
sizeOf(new A1())=16
sizeOf(new A())=16
sizeOf(new B())=24
sizeOf(new B2())=24
sizeOf(new B[3])=32
sizeOf(new C())=24
fullSizeOf(new C())=128
sizeOf(new D())=32
fullSizeOf(new D())=64
sizeOf(new int[3])=32
sizeOf(new Integer(1)=16
sizeOf(new Integer[0])=16
sizeOf(new Integer[1])=24
sizeOf(new Integer[2])=24
sizeOf(new Integer[3])=32
sizeOf(new Integer[4])=32
sizeOf(new A[3])=32
sizeOf(new E())=24

关闭指针压缩

D:\ideaproject\size-agent\target>java -javaagent:test.jar -XX:-UseCompressedOops -jar test.jar
sizeOf(new A1())=16
sizeOf(new A())=24
sizeOf(new B())=24
sizeOf(new B2())=32
sizeOf(new B[3])=48
sizeOf(new C())=40
fullSizeOf(new C())=160
sizeOf(new D())=40
fullSizeOf(new D())=88
sizeOf(new int[3])=40
sizeOf(new Integer(1)=24
sizeOf(new Integer[0])=24
sizeOf(new Integer[1])=32
sizeOf(new Integer[2])=40
sizeOf(new Integer[3])=48
sizeOf(new Integer[4])=56
sizeOf(new A[3])=48
sizeOf(new E())=32

大家还可以根据自己想要了解的情况去更改上述代码,用来验证自己的理论知识是否正确。

参考

《深入理解java虚拟机》

https://www.iteye.com/blog/yueyemaitian-2033046