问题

一个对象的结构是什么样的?

答案

核心概念

Java对象在JVM内存中的结构由三部分组成:对象头(Header)实例数据(Instance Data)对齐填充(Padding)。这个结构设计充分考虑了内存对齐、垃圾回收和性能优化。

对象结构详解

1. 对象头(Object Header)

对象头是对象的关键信息存储区域,占用空间与JVM位数相关:

32位JVM对象头结构

| Mark Word (4 bytes) | 类型指针 (4 bytes) | 数组长度 (4 bytes,仅数组对象) |

64位JVM对象头结构

| Mark Word (8 bytes) | 类型指针 (8 bytes) | 数组长度 (4 bytes,仅数组对象) | 对齐填充 (4 bytes) |
Mark Word 结构

Mark Word存储了对象的运行时数据,在不同状态下结构不同:

// 64位JVM Mark Word示例(简化版)
public class MarkWordStructure {
    // 无锁状态
    // | unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2 |

    // 偏向锁状态
    // | thread_id:54 | epoch:2 | unused:1 | age:4 | biased_lock:1 | lock:1 |

    // 轻量级锁状态
    // | ptr_to_lock_record:62 | lock:2 |

    // 重量级锁状态
    // | ptr_to_heavyweight_monitor:62 | lock:2 |

    // GC标记状态
    // | ForwardingAddress:62 | lock:2 |
}

Mark Word包含信息

  • HashCode:对象的哈希码
  • GC分代年龄:记录对象在GC中的存活次数(最大15)
  • 锁状态标志:无锁、偏向锁、轻量级锁、重量级锁
  • 偏向锁线程ID:持有偏向锁的线程标识
  • 锁记录指针:轻量级锁时指向栈中锁记录
  • 重量级锁指针:重量级锁时指向对象监视器
类型指针(Class Pointer)

指向对象所属类的元数据信息:

public class Person {
    private String name;
}

Person person = new Person();
// 对象头的类型指针指向Person.class的元数据

2. 实例数据(Instance Data)

实例数据存储对象的真正有效信息,包括:

字段存储规则

  • 相同宽度的字段:分配在一起
  • 父类字段:在子类字段之前
  • 内存对齐:long/double等8字节字段要求8字节对齐
public class Example {
    // 字段按内存对齐和顺序存储
    private byte   b1;    // 1 byte
    private int    i;     // 4 bytes
    private long   l;     // 8 bytes(需要8字节对齐)
    private byte   b2;    // 1 byte
    private double d;     // 8 bytes(需要8字节对齐)
    private short  s;     // 2 bytes
}

// 内存布局示例(64位JVM):
// | b1 | padding(3) | i | l | b2 | padding(7) | d | s | padding(6) |

字段重排序优化

JVM可能对字段进行重排序以优化内存使用:

public class OptimizedLayout {
    // JVM可能重排序为:long, double, int, short, byte, byte
    private byte b1;      // 1 byte
    private long l;       // 8 bytes
    private byte b2;      // 1 byte
    private double d;     // 8 bytes
    private int i;        // 4 bytes
    private short s;      // 2 bytes
}

3. 对齐填充(Padding)

对齐填充不是必需的,但用于保证对象大小是8字节的整数倍:

public class SmallObject {
    private byte b; // 只占用1字节
}

// 实际内存布局(64位JVM,开启压缩指针):
// | Mark Word (8) | 类型指针 (4) | 实例数据 (1) | 对齐填充 (3) |
// 总计:16 bytes(8字节对齐)

内存布局示例

普通对象内存占用计算

public class MemoryExample {
    public static void main(String[] args) {
        // 基础对象内存占用
        Object obj = new Object();
        // 64位JVM + 压缩指针:Mark Word(8) + 类型指针(4) + 对齐填充(4) = 16 bytes

        // 自定义对象
        class Person {
            int age;        // 4 bytes
            String name;    // 4 bytes(引用类型,压缩指针)
        }
        Person person = new Person();
        // 总计:16(对象头) + 4(age) + 4(name) + 4(对齐) = 28 bytes → 32 bytes(8字节对齐)
    }
}

数组对象特殊结构

public class ArrayStructure {
    public static void main(String[] args) {
        int[] array = new int[5];

        // 数组对象结构:
        // | Mark Word (8) | 类型指针 (4) | 数组长度 (4) | 数据区域 (20) | 对齐填充 (0) |
        // 总计:36 bytes → 40 bytes(8字节对齐)

        // 对象数组
        String[] strArray = new String[5];
        // 数据区域:5 * 4 = 20 bytes(引用大小)
        // 总计:36 bytes → 40 bytes
    }
}

性能优化考虑

1. 内存对齐优化

// 不好的设计:内存碎片
public class BadDesign {
    private byte b1;
    private long l;
    private byte b2;
    private double d;
}

// 优化设计:减少内存碎片
public class GoodDesign {
    private long l;
    private double d;
    private byte b1;
    private byte b2;
}

2. 字段访问性能

字段在内存中的连续性影响缓存性能:

public class CacheFriendly {
    // 经常一起访问的字段放在一起
    private int id;
    private int count;
    private long timestamp;

    // 不经常访问的字段放后面
    private String description;
    private Object metadata;
}

面试要点总结

  1. 对象三部分:对象头 + 实例数据 + 对齐填充
  2. 对象头组成:Mark Word + 类型指针 [+ 数组长度]
  3. Mark Word作用:存储哈希码、GC年龄、锁状态等
  4. 实例数据规则:按宽度分组、父类在前、内存对齐
  5. 内存对齐:保证8字节边界,提高访问效率
  6. 数组特殊:额外存储数组长度信息

理解对象结构有助于:

  • 精确计算内存占用
  • 优化字段排列提升缓存性能
  • 理解锁机制和GC原理
  • 分析内存泄漏问题

这是JVM内存管理的基础知识,对性能调优和问题诊断都很重要。