作者: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.jarsizeOf(new A1())=16sizeOf(new A())=16sizeOf(new B())=24sizeOf(new B2())=24sizeOf(new B[3])=32sizeOf(new C())=24fullSizeOf(new C())=128sizeOf(new D())=32fullSizeOf(new D())=64sizeOf(new int[3])=32sizeOf(new Integer(1)=16sizeOf(new Integer[0])=16sizeOf(new Integer[1])=24sizeOf(new Integer[2])=24sizeOf(new Integer[3])=32sizeOf(new Integer[4])=32sizeOf(new A[3])=32sizeOf(new E())=24

敞开指针压缩

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

大家还能够依据本人想要理解的状况去更改上述代码,用来验证本人的理论知识是否正确。

参考

《深刻了解java虚拟机》

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