Java 对象头

本贴最后更新于 1826 天前,其中的信息可能已经渤澥桑田

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

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

一个对象在内存中的布局

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

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

在 32 位的系统中 MarkWordKlass 都是 4 字节

但是在 64 位的系统中 MarkWordKlass 都占用 8 字节

若在 64 为的系统中开启了指针压缩 +UseCompressedOopsMarkWord 占 8 字节,Klass 占 4 字节

例子:

// 64位系统,开启了指针压缩 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("未知错误"); } }

我这份代码只能计算当前对象的大小,不会去递归计算引用的对象,而且没有包含计算数组的情况,至少应该加上 String 成员变量大小的递归计算的。。。我忘了

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

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

  • Java

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

    3200 引用 • 8216 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...