Java 对象头

以下的内容都是基于 32 位 JDK,或者 64 位 JDK 并且开启了指针压缩

如果使用 64 位 JDK 并且没有开启指针压缩,那么 MarkWord 和 Klass 都会变为 8 字节

一个对象在内存中的布局

对象在内存中的布局可以分为两种情况,一种是普通对象,一种是数组对象
image.png
在 jvm 中所有的对象大小都是 8 字节的整数倍,所以对象会有一个对其填充数据

数组对象的对象头除了 MarkWordKlass 指针外还有一个 4 字节的 length 用来表示数组的大小

例子:

    class Student{
        private String name;
        private boolean boy;
    }
    
    Student s = new Student();
    
    现在我们可以计算出s的大小为
    12(对象头) + 4(name的引用长度) + 1(boolean为基本类型只有1字节) + 7(padding)= 24

Mark Word

image.png

计算对象大小

基于上面的知识,我们可以使用 unsafe 来计算一个对象的大小,代码:

public class ObjectSizeUtil {

    private static Map<Class, Long> map = new HashMap<>(16);

    static {
        map.put(byte.class, 1L);
        map.put(char.class, 1L);
        map.put(boolean.class, 1L);
        map.put(short.class, 2L);
        map.put(int.class, 4L);
        map.put(float.class, 4L);
        map.put(double.class, 8L);
        map.put(long.class, 8L);
    }

    /**
     * 获取对象的大小,单位字节
     * 不会递归计算对象的大小
     */
    public static long sizeOf(Object o) {
        Unsafe unsafe = reflectionGetUnsafe();
        Field[] fields = o.getClass().getDeclaredFields();
        long size;

        long position = 0;
        Field lastField = fields[fields.length - 1];
        for (Field field : fields) {
            // 静态变量不属于对象的大小,不计算
            if (Modifier.isStatic(field.getModifiers())) continue;
            long l = unsafe.objectFieldOffset(field);
            if (l >= position) {
                position = l;
                lastField = field;
            }
        }

        Class lastFieldType = lastField.getType();
        if (!lastFieldType.isPrimitive()) {
            size = position + 4;
        } else {
            size = position + primitiveSize(lastFieldType);
        }
        return padding(size);
    }

    /**
     * 将结果对齐,因为在jvm中对象大小都是8字节的倍数
     * @return 对象的大小,单位字节
     */
    private static long padding(long size) {
        if (size % 8 == 0) {
            return size;
        }
        return (size / 8 + 1) * 8;
    }

    private static long primitiveSize(Class lastFieldClass) {
        return map.get(lastFieldClass);
    }

    /**
     * 使用反射的方式获取Unsafe
     */
    private static Unsafe reflectionGetUnsafe() {
        try {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            return (Unsafe) field.get(null);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
        throw new IllegalArgumentException("未知错误");
    }
}

我这份代码只能计算当前对象的大小,不会去递归计算引用的对象,而且没有包含计算数组的情况

当然,如果要计算数组对象大小也很简单咯,在外部遍历数组,多次调用该方法即可

计算原理
使用 unsafe 获取最后一个字段的偏移位置,然后再加上最后一个字段的大小就等于对象的大小

  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    2621 引用 • 8019 回帖 • 775 关注
回帖
请输入回帖内容...