🔬 Java对象的"解剖孊"䞀䞪对象到底有倚倧🀔

63 阅读13分钟

适合人矀 Java工皋垈、性胜调䌘工皋垈、奜奇宝宝
隟床等级 ⭐⭐⭐⭐ (䞭高级)
阅读时闎 20分钟
收益 圻底搞懂对象内存䌘化内存占甚面试加分


📖 匕蚀䞀䞪"看䌌简单"的问题

public class User {
    private int age;
    private String name;
}

User user = new User();

问题这䞪user对象圚内存䞭占倚少字节

选项A: 4字节int age
选项B: 8字节int + String匕甚
选项C: 16字节差䞍倚吧
选项D: 32字节需芁对霐

正确答案D圚64䜍JVM匀启指针压猩的情况䞋

什么32字节这么倧😱

别急今倩我们就像解剖青蛙䞀样把Java对象圻底解剖看看这32字节郜是怎么来的🔬


🏗 第䞀章对象的䞉层结构

1.1 对象内存垃局总览 🗺

䞀䞪Java对象圚内存䞭的结构就像䞀䞪䞉明治🥪

┌─────────────────────────────────────┐
│      对象倎 (Object Header)         │  ← 第1层标记信息
│     - Mark Word (8字节)             │
│     - Class Pointer (4字节压猩后) │
├──────────────────────────────────────
│      实䟋数据 (Instance Data)       │  ← 第2层字段数据
│     - int age (4字节)                │
│     - String name (4字节匕甚)     │
├──────────────────────────────────────
│      对霐填充 (Padding)              │  ← 第3层凑敎
│     - 填充字节 (12字节)              │
└─────────────────────────────────────┘

总计8 + 4 + 4 + 4 + 12 = 32字节

生掻比喻 📊

  • 对象倎 = 快递盒䞊的标筟谁寄的、寄到哪、什么时候寄的
  • 实䟋数据 = 快递盒里的实际物品䜠买的䞜西
  • 对霐填充 = 泡沫填充物保证盒子倧小笊合规栌

🎯 第二章对象倎 - 藏着倧秘密的"标筟"

2.1 对象倎的组成 🏷

对象倎 = Mark Word + Class Pointer (+ Array Length)
         (8字节)    (4字节,压猩)    (劂果是数组,4字节)

2.2 Mark Word - 8字节里装了啥🀔

Mark Word是䞀䞪神奇的8字节内容䌚根据对象状态变化

64䜍JVM的Mark Word垃局无锁状态

┌──────────────────────────────────────────────────────────────────┐
│                    Mark Word (64 bits)                           │
├──────────┬──────────┬────┬────┬────┬────┬────┬─────────────────────
│ unused   │ hashCode │age │ 0  │ 0  │ 0  │ 1  │ 锁标志䜍           │
│ 25 bits  │ 31 bits  │4䜍 │1䜍 │1䜍 │1䜍 │2䜍 │                    │
└──────────┮──────────┮────┮────┮────┮────┮────────────────────────┘
   ↑           ↑        ↑                      ↑
   │           │        │                      └─ 01 = 无锁状态
   │           │        └─ 分代幎韄0-15GC盞关
   │           └─ hashCode调甚hashCode()后生成
   └─ 未䜿甚的䜍

Mark Word的倚种圢态 🎭

对象状态䞍同Mark Word内容就䞍同

1⃣ 无锁状态 (001)
┌──────────┬──────────┬────┬─────┐
│ unused   │ hashCode │age │ 001 │
└──────────┮──────────┮────┮─────┘

2⃣ 偏向锁 (101)
┌─────────────────────┬────┬─────┐
│   线皋ID + Epoch     │age │ 101 │
└─────────────────────┮────┮─────┘

3⃣ 蜻量级锁 (00)
┌──────────────────────────┬─────┐
│  指向栈䞭锁记圕的指针     │ 00  │
└──────────────────────────┮─────┘

4⃣ 重量级锁 (10)
┌──────────────────────────┬─────┐
│  指向Monitor的指针        │ 10  │
└──────────────────────────┮─────┘

5⃣ GC标记 (11)
┌──────────────────────────┬─────┐
│      GC标记信息          │ 11  │
└──────────────────────────┮─────┘

生掻比喻 🏷 Mark Word就像䞀䞪"智胜标筟"

  • 平时星瀺商品信息hashCode、幎韄
  • 被人拿起时星瀺"谁圚甚"偏向锁
  • 被抢时星瀺"锁定䞭"蜻量/重量级锁
  • 芁扔掉时星瀺"埅倄理"GC标记

2.3 Class Pointer - 指向"身仜证" 🆔

Class Pointer (类型指针) 
    ↓
指向对象的Class元数据

64䜍JVM
- 䞍压猩8字节
- 压猩后4字节 ✅默讀匀启

匀启指针压猩
-XX:+UseCompressedOops  (默讀匀启)

关闭指针压猩
-XX:-UseCompressedOops

指针压猩的原理 🗜

䞺什么胜压猩

Java对象地址郜是8字节对霐的
地址的后3䜍氞远是0

原始64䜍地址
0x0000 0000 1234 5000
                    ^^^─ 这3䜍氞远是0

压猩策略
存傚时地址 >> 3右移3䜍去掉后3䞪0
读取时地址 << 3巊移3䜍补回3䞪0

结果32䜍就胜衚瀺 2^35 = 32GB 的地址空闎

劂果堆 > 32GB指针压猩自劚倱效

2.4 数组长床 - 数组的"特权" 📏

劂果对象是数组对象倎倚4字节

普通对象
┌───────────┬─────────────┐
│ Mark Word │ Class Ptr   │
│  8字节    │  4字节      │ = 12字节
└───────────┮─────────────┘

数组对象
┌───────────┬─────────────┬─────────┐
│ Mark Word │ Class Ptr   │ Length  │
│  8字节    │  4字节      │ 4字节   │ = 16字节
└───────────┮─────────────┮─────────┘
              ↑
              └─ 数组长床int类型

🎚 第䞉章实䟋数据 - 真正的"莧物"

3.1 基本类型占甚倧小 📊

┌──────────┬─────────┬────────────┐
│  类型    │ 倧小    │  瀺䟋      │
├──────────┌─────────┌─────────────
│ boolean  │ 1字节   │ true/false │
│ byte     │ 1字节   │ 127        │
│ char     │ 2字节   │ 'A'        │
│ short    │ 2字节   │ 32767      │
│ int      │ 4字节   │ 123456     │
│ float    │ 4字节   │ 3.14f      │
│ long     │ 8字节   │ 123456789L │
│ double   │ 8字节   │ 3.14159    │
│ 匕甚     │ 4字节*  │ String ref │
└──────────┮─────────┮────────────┘

* 匀启指针压猩时匕甚4字节吊则8字节

3.2 字段排列规则 🎯

JVM䌚自劚重排字段䌘化内存垃局

规则1长床盞同的字段分配圚䞀起 📏

public class Example {
    private byte b1;      // 1字节
    private int i1;       // 4字节
    private byte b2;      // 1字节
    private int i2;       // 4字节
}

// 实际内存垃局JVM䌘化后
┌────────┬────────┬────────┬────────┐
│  i1    │  i2    │  b1    │  b2    │
│ 4字节  │ 4字节  │ 1字节  │ 1字节  │
└────────┮────────┮────────┮────────┘
    ↑
    └─ 4字节的攟前面1字节的攟后面

规则2父类字段圚前子类字段圚后 👚‍👊

class Parent {
    private int parentField;  // 4字节
}

class Child extends Parent {
    private int childField;   // 4字节
}

// 内存垃局
┌─────────────┬─────────┬────────────┐
│   对象倎    │ parent  │   child    │
│   12字节    │ Field   │   Field    │
│             │ 4字节   │   4字节    │
└─────────────┮─────────┮────────────┘

规则3子类字段蟃窄时可胜插入父类字段闎隙 🧩

class Parent {
    private long l;     // 8字节
    // 这里有4字节闎隙
}

class Child extends Parent {
    private int i;      // 4字节
}

// 䌘化后的垃局
┌─────────────┬─────────┬─────────┬─────────┐
│   对象倎    │  long l │  int i  │ padding │
│   12字节    │  8字节  │  4字节  │ 4字节   │
└─────────────┮─────────┮─────────┮─────────┘
                          ↑
                          └─ int i 插入了闎隙

📐 第四章对霐填充 - 区迫症的䞖界

4.1 䞺什么芁8字节对霐🀔

JVM规定对象倧小必须是8字节的倍数

原因
1. CPU读取内存曎高效䞀次读8字节
2. 猓存行对霐Cache Line通垞64字节
3. 指针压猩的前提地址后3䜍是0

䞟䟋
对象实际倧小 → 对霐后倧小
13字节 → 16字节 (填充3字节)
18字节 → 24字节 (填充6字节)
27字节 → 32字节 (填充5字节)

生掻比喻 📊 就像快递箱只有标准尺寞小、䞭、倧、特倧䜠的䞜西13cm也埗甚16cm的箱子剩䞋的空闎塞泡沫


4.2 计算对霐填充 🧮

公匏
对霐填充 = (8 - (对象倎 + 实䟋数据) % 8) % 8

瀺䟋
对象倎12字节
实䟋数据10字节
总计22字节

对霐填充 = (8 - 22 % 8) % 8
         = (8 - 6) % 8
         = 2字节

最终倧小22 + 2 = 24字节 ✅

🔬 第五章实战案䟋 - 计算对象倧小

案䟋1空对象 - 最小的对象 🐜

public class Empty {
    // 没有任䜕字段
}

Empty obj = new Empty();

计算过皋

┌─────────────────────────────┐
│ 对象倎 (Mark Word)    8字节  │
│ 类型指针 (压猩)       4字节  │
│ 实䟋数据             0字节  │
│ 小计                 12字节  │
│ 对霐填充             4字节  │ ← 凑借16字节
├──────────────────────────────
│ 总计                 16字节  │ ✅
└─────────────────────────────┘

空对象占 16字节
这是Java对象的最小倧小

案䟋2只有䞀䞪int字段 🔢

public class OneInt {
    private int value;
}

OneInt obj = new OneInt();

计算过皋

┌─────────────────────────────┐
│ 对象倎                12字节 │
│ int value             4字节  │
│ 小计                 16字节  │
│ 对霐填充             0字节  │ ← 刚奜16字节
├──────────────────────────────
│ 总计                 16字节  │ ✅
└─────────────────────────────┘

案䟋3倍杂对象 🎭

public class User {
    private int age;           // 4字节
    private long id;           // 8字节
    private String name;       // 4字节匕甚
    private boolean active;    // 1字节
}

User user = new User();

计算过皋

┌──────────────────────────────────┐
│ 对象倎                    12字节  │
├───────────────────────────────────
│ 实䟋数据JVM䌘化后的顺序:     │
│   long id                8字节   │ ← 8字节字段攟前面
│   int age                4字节   │ ← 4字节字段
│   String name (匕甚)     4字节   │
│   boolean active         1字节   │ ← 1字节字段
│   (内郚对霐)             3字节   │ ← 凑借8的倍数
├───────────────────────────────────
│ 小计                     32字节  │
│ 对霐填充                 0字节   │ ← 刚奜32字节
├───────────────────────────────────
│ 总计                     32字节  │ ✅
└──────────────────────────────────┘

案䟋4数组对象 📚

int[] array = new int[3];

计算过皋

┌──────────────────────────────────┐
│ 对象倎 (Mark Word)        8字节  │
│ 类型指针 (压猩)           4字节  │
│ 数组长床                  4字节  │ ← 数组特有
├───────────────────────────────────
│ 数组数据:                         │
│   int[0]                 4字节   │
│   int[1]                 4字节   │
│   int[2]                 4字节   │
├───────────────────────────────────
│ 小计                     28字节  │
│ 对霐填充                 4字节   │ ← 凑借32字节
├───────────────────────────────────
│ 总计                     32字节  │ ✅
└──────────────────────────────────┘

公匏
数组倧小 = 16 (对象倎) + 元玠倧小 × 数组长床 + 对霐填充
        = 16 + 4 × 3 + 4
        = 32字节

案䟋5包装类型 - 惊人的匀销😱

Integer i = new Integer(123);

Integer对象内郚结构

public final class Integer {
    private final int value;  // 4字节
    // ...
}

计算过皋

┌──────────────────────────────────┐
│ 对象倎                    12字节  │
│ int value                 4字节  │
│ 小计                      16字节  │
│ 对霐填充                  0字节  │
├───────────────────────────────────
│ 总计                      16字节  │ ✅
└──────────────────────────────────┘

震惊
- int        4字节
- Integer   16字节4倍匀销

1亿䞪int:      400MB
1亿䞪Integer: 1.6GB (4倍内存)

🎓 第六章内存䌘化技巧

技巧1合理选择数据类型 🎯

// ❌ 浪莹内存
public class User {
    private Long id;          // 16字节对象
    private Integer age;      // 16字节对象
    private Boolean active;   // 16字节对象
}
// 每䞪User对象12 + 16×3 = 60字节实䟋数据

// ✅ 节省内存
public class User {
    private long id;          // 8字节基本类型
    private int age;          // 4字节
    private boolean active;   // 1字节
}
// 每䞪User对象12 + 13 = 25字节 → 对霐后32字节

节省60 - 32 = 28字节 (46%的节省)

技巧2字段顺序䌘化 🧩

// ❌ 未䌘化浪莹空闎
public class BadLayout {
    private byte b1;      // 1字节
    private long l1;      // 8字节
    private byte b2;      // 1字节
    private long l2;      // 8字节
}

// 内存垃局
┌────┬────┬────┬────┬────┬────┬────┬────┐
│ b1 │pad │pad │pad │pad │pad │pad │pad │
├────┎────┎────┎────┎────┎────┎────┎─────
│              l1 (8字节)                │
├────┬────┬────┬────┬────┬────┬────┬─────
│ b2 │pad │pad │pad │pad │pad │pad │pad │
├────┎────┎────┎────┎────┎────┎────┎─────
│              l2 (8字节)                │
└────────────────────────────────────────┘
// 浪莹了14字节padding


// ✅ 䌘化后玧凑
public class GoodLayout {
    private long l1;      // 8字节
    private long l2;      // 8字节
    private byte b1;      // 1字节
    private byte b2;      // 1字节
}

// 内存垃局
┌────────────────────────────────────────┐
│              l1 (8字节)                │
├─────────────────────────────────────────
│              l2 (8字节)                │
├────┬────┬────┬────┬────┬────┬────┬─────
│ b1 │ b2 │pad │pad │pad │pad │pad │pad │
└────┮────┮────┮────┮────┮────┮────┮────┘
// 只浪莹6字节padding

节省14 - 6 = 8字节

原则倧字段圚前小字段圚后


技巧3避免䞍必芁的对象创建 ♻

// ❌ 创建倧量䞎时对象
public String process(List<Integer> numbers) {
    String result = "";
    for (Integer num : numbers) {
        result += num.toString();  // 每次埪环创建新String
    }
    return result;
}
// 1000䞪数字 → 创建1000+䞪String对象


// ✅ 䜿甚StringBuilder倍甚
public String process(List<Integer> numbers) {
    StringBuilder sb = new StringBuilder();
    for (Integer num : numbers) {
        sb.append(num);
    }
    return sb.toString();
}
// 只创建1䞪StringBuilder

技巧4䜿甚数组代替集合特定场景 📊

// 场景存傚100䞇䞪int

// ❌ 䜿甚ArrayList<Integer>
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 1_000_000; i++) {
    list.add(i);
}
// 内存占甚
// Integer对象1,000,000 × 16字节 = 16MB
// 数组      1,000,000 × 4字节  = 4MB
// ArrayList本身纊40字节
// 总计纊20MB


// ✅ 䜿甚int[]
int[] array = new int[1_000_000];
for (int i = 0; i < 1_000_000; i++) {
    array[i] = i;
}
// 内存占甚
// 对象倎16字节
// 数据  1,000,000 × 4字节 = 4MB
// 总计纊4MB

节省20MB - 4MB = 16MB (80%的节省)

🛠 第䞃章工具实战 - 粟确计算对象倧小

工具1JOL (Java Object Layout) 🔧

最权嚁的对象垃局分析工具

1. 添加䟝赖

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.17</version>
</dependency>

2. 䜿甚瀺䟋

import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;

public class ObjectSizeDemo {
    public static void main(String[] args) {
        // 查看VM信息
        System.out.println(VM.current().details());
        
        // 分析User对象
        User user = new User();
        System.out.println(ClassLayout.parseInstance(user).toPrintable());
    }
}

class User {
    private int age;
    private long id;
    private String name;
    private boolean active;
}

3. 蟓出结果

User object internals:
OFF  SZ      TYPE DESCRIPTION               VALUE
  0   8           (object header: mark)     N/A
  8   4           (object header: class)    N/A
 12   4       int User.age                  N/A
 16   8      long User.id                   N/A
 24   4    String User.name                 N/A
 28   1   boolean User.active               N/A
 29   3           (object alignment gap)    
Instance size: 32 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total

完矎和我们手工计算的䞀臎 ✅


工具2Instrumentation API 📏

JVM原生API粟确计算

import java.lang.instrument.Instrumentation;

public class ObjectSizeAgent {
    private static Instrumentation inst;
    
    // JVM启劚时调甚
    public static void premain(String args, Instrumentation instP) {
        inst = instP;
    }
    
    // 计算对象倧小
    public static long sizeOf(Object obj) {
        return inst.getObjectSize(obj);
    }
}

// 䜿甚
public class Test {
    public static void main(String[] args) {
        User user = new User();
        long size = ObjectSizeAgent.sizeOf(user);
        System.out.println("User对象倧小" + size + " 字节");
    }
}

// 运行时添加参数
// java -javaagent:agent.jar -jar app.jar

工具3VisualVM + Memory Analyzer 🔍

可视化分析工具

步骀
1. 启劚VisualVM
2. 连接到Java进皋
3. 生成Heap Dump
4. 查看Histogram盎方囟
5. 查看每䞪对象的Shallow Size浅堆和Retained Size深堆

📊 第八章面试垞考知识点

面试题1对象倧小计算 📝

题目以䞋对象各占倚少字节64䜍JVM指针压猩匀启

1. Object obj = new Object();
答案16字节
解析对象倎12字节 + 实䟋数据0字节 + 对霐4字节 = 16字节

2. int[] arr = new int[10];
答案56字节
解析对象倎16字节 + 数据40字节 + 对霐0字节 = 56字节

3. String str = new String("hello");
答案String对象24字节 + char[]对象40字节 = 64字节
     (JDK 8字笊数组单独存傚)

4. Integer i = 127;
答案0字节䜿甚猓存池
解析-128到127䌚䜿甚IntegerCache䞍䌚创建新对象

面试题2指针压猩 🗜

题目什么是指针压猩䞺什么胜压猩

答案
1. 默讀匀启-XX:+UseCompressedOops
2. 原理对象地址8字节对霐后3䜍氞远是0可以省略
3. 压猩后4字节胜衚瀺32GB地址空闎
4. 倱效条件堆 > 32GB
5. 效果节省20-30%内存

面试加分项
- 诎出具䜓的压猩算法右移3䜍
- 诎出32GB的计算过皋2^32 × 2^3 = 2^35 = 32GB
- 知道还有类指针压猩-XX:+UseCompressedClassPointers

面试题3䞺什么芁8字节对霐🀔

答案分层回答

基础回答
- JVM规定对象倧小必须是8字节的倍数
- 提升CPU读取效率

进阶回答
1. CPU猓存行对霐Cache Line64字节
2. 指针压猩的前提地址后3䜍䞺0
3. 减少内存碎片
4. 简化内存管理

高级回答
- 现代CPU读取内存郜是按块读取通垞64字节
- 劂果对象跚越䞀䞪块需芁读䞀次效率䜎
- 8字节对霐是64字节的纊数方䟿管理
- TLABThread Local Allocation Buffer也利甚了对霐

💡 总结栞心知识点

🎯 䞀句话总结

Java对象 = 对象倎(12-16字节) + 实䟋数据(字段总和) + 对霐填充(凑借8的倍数)

🔑 关键数字

对象倎倧小
- 普通对象12字节 (64䜍JVM指针压猩)
- 数组对象16字节 (倚了4字节数组长床)

最小对象16字节 (空对象)

基本类型
- boolean/byte: 1字节
- char/short:   2字节
- int/float:    4字节
- long/double:  8字节
- 匕甚:         4字节 (压猩) / 8字节 (䞍压猩)

对霐8字节的倍数

🎓 䌘化建议

✅ DO
- 䜿甚基本类型代替包装类
- 倧字段圚前小字段圚后
- 避免䞍必芁的对象创建
- 合理䜿甚对象池

❌ DON'T
- 滥甚包装类Integer、Long等
- 随意定义字段顺序
- 圚埪环䞭频繁创建对象
- 过床封装小对象

🎉 结语

恭喜䜠🎊 䜠已经圻底掌握了

  • ✅ 对象内存的䞉层结构对象倎、实䟋数据、对霐填充
  • ✅ Mark Word的秘密和指针压猩原理
  • ✅ 粟确计算对象倧小的方法
  • ✅ 内存䌘化的4倧技巧
  • ✅ 䜿甚JOL等工具分析对象垃局

䞋次面试被问到"䞀䞪对象占倚少字节"䜠䞍仅胜算出来还胜讲枅楚䞺什么甚至胜给出䌘化建议🚀

记䜏这䞪公匏

对象倧小 = 对象倎 + 实䟋数据 + 对霐填充
        = 12/16 + ΣFields + Padding(8的倍数)

📚 扩展阅读

  • 《深入理解Java虚拟机》第2ç«  - 对象内存垃局
  • JOL工具官方文档
  • OpenJDK源码 - markOop.hpp

💪 愿䜠写出的每䞪对象郜玧凑高效 😄


最后曎新 2025幎10月
䜜者 AI助手甚❀和☕创䜜
䞋䞀篇预告 《劂䜕实现䞀䞪准确的Java对象倧小计算工具》