从一个例子看内存布局

先看一个例子:(Win32 VS平台)

第一种情况:

class Base

{
public:
virtual void Func1() {};

};


class Derived: public Base
{
public:
virtual void Func2() {};
};


请问,sizeof(Derived)==?

相信这个很多人都可以知道答案4,这里Func1(),Func2()地址都放在Derived的虚函数表中。内存布局如下所示:

Derived 虚函数表指针   4         ----> | Base::Func1()  |  Derived::Func2() |



如果改成这样:

第二中情况:

class Base

{
public:
virtual void Func1() {};

};


class Derived: public virtual Base
{
public:
virtual void Func2() {};
};


只增加了个virtual关键字,sizeof(Derived)等于多少呢?

按照惯性思维,虚继承应该增加一个虚基类指针vbptr,大小在原来的基础上应增加4,为8。

但是在VS上调试却是12。这个就值得我们考虑下了。。。


通过修改编译选项在编译时即可查看内存布局,发现:

虚继承的时候,Func1()函数不在Derived的虚函数表中,而在其基类的虚函数表中。

这里涉及到虚继承的意义,是为了避免二义性。

假设将Func1()放入Derived的虚函数表中,按照此规则,再有一个类(Derived2)继承自Base,那么Func1()会放入Derived2的虚函数表中,

如果有一个类(Triple)继承自Derived和Derived2,那么Triple 的对象调用Func1()该如何调用呢?因为有2个Func1()

从这里我们可以推翻上面的假设。

这个时候编译器做了优化,给Base单独分配一个虚函数表指针。排列顺序是:

Derived 虚函数表指针  4         ----> | Derived::Func2() |

Derived 虚基类指针      4         

Base 虚函数表指针      4          ----> | Base::Func1() |




<think>好的,我现在需要帮助用户了解Java对象的内存布局。根据用户的问题,他们可能对对象在内存中的具体结构感兴趣,包括对象头、实例数据、对齐填充等部分。首先,我应该回顾已有的知识,确保自己正确理解这些概念。 首先,用户提供的引用[1]和[2]中提到了对象头、Mark Word、型指针、实例数据和对齐填充。这些是构成Java对象内存布局的主要部分。用户可能想知道每个部分的作用以及它们如何组合在一起。我需要将这些内容结构化,用简单明了的中文解释清楚。 接下来,我需要考虑如何组织回答。用户要求结构清晰,分步说明。因此,可以按照对象内存布局的组成部分逐一介绍。比如,先介绍对象头,再分解为Mark Word和型指针,然后是实例数据,最后是对齐填充。每个部分需要说明其用途和存储的内容,例如Mark Word在不同锁状态下的变化,型指针的作用等。 用户可能需要示例来更好地理解。比如,引用[1]中的代码示例展示了如何使用JOL工具查看对象内存布局。我应该提到这个工具,并建议用户使用它来实际观察内存结构。同时,需要确保所有的术语解释准确,比如偏向锁、轻量级锁和重量级锁在对象头中的表示。 另外,用户的问题可能涉及到内存对齐的概念。需要解释为什么需要对齐填充,以及它如何影响内存使用和性能。这部分可能需要用简短的例子说明,比如对象大小必须是8字节的倍数,不足的部分用填充补足。 还要注意引用文献的正确应用。在回答中提到的每个关键点,如对象头的结构,应该自然地添加引用标识,例如[^1]或[^2]。需要确保引用标记的位置正确,不会干扰阅读的流畅性。 最后,生成相关问题。根据用户的问题,相关的问题可能包括对象内存布局对性能的影响,如何优化内存使用,或者不同锁状态下的内存变化。需要列出几个相关问题,帮助用户进一步探索主题。 总结步骤: 1. 分部分介绍Java对象内存布局:对象头(Mark Word,型指针,数组长度)、实例数据、对齐填充。 2. 使用JOL工具示例说明如何查看内存布局。 3. 解释各部分的作用和内容,尤其是Mark Word在不同锁状态下的结构。 4. 讨论内存对齐的原因和影响。 5. 添加相关引用标记。 6. 生成相关问题,如性能影响、优化方法、工具使用等。 需要确保回答准确,术语正确,并且符合用户的中文需求。同时,保持回答的结构清晰,易于理解。</think>### Java对象内存布局详解 Java对象在内存中的布局分为**对象头**、**实例数据**和**对齐填充**三部分[^2]。以下是分步说明: --- #### 一、对象头(Header) 对象头包含**Mark Word**、**型指针**和**数组长度**(仅数组对象): 1. **Mark Word** 存储对象自身的运行时数据,包括: - 哈希码(HashCode) - GC分代年龄 - 锁状态标志(偏向锁、轻量级锁、重量级锁) - 线程持有的偏向锁ID - 偏向时间戳 在不同锁状态下,Mark Word的内容会动态变化。例如: - **无锁状态**:存储哈希码和分代年龄。 - **偏向锁**:存储持有锁的线程ID和偏向时间戳。 - **轻量级锁**:指向栈中锁记录的指针。 - **重量级锁**:指向互斥量(Monitor)的指针。 2. **型指针(Klass Pointer)** 指向方法区中的元数据(Klass),用于确定对象的型。在64位JVM中默认开启指针压缩,型指针占用4字节[^2]。 3. **数组长度(仅数组对象)** 如果是数组对象,额外存储数组长度(4字节)。 --- #### 二、实例数据(Instance Data) 存储对象的所有成员变量: - 基本型按实际大小分配(如`int`占4字节)。 - 引用型在64位JVM中占4字节(指针压缩开启时)或8字节(未开启时)[^1]。 - 字段按默认对齐规则排列(如`long`按8字节对齐)。 --- #### 三、对齐填充(Padding) 为了满足JVM的内存对齐要求(对象大小必须是8字节的整数倍),在实例数据之后填充空白字节[^1]。 --- #### 示例分析 使用JOL(Java Object Layout)工具查看对象布局: ```java public class User { int age; // 4字节 boolean flag; // 1字节 String name; // 引用型(4字节,指针压缩开启) } // 输出对象布局: System.out.println(ClassLayout.parseInstance(new User()).toPrintable()); ``` 输出结果可能为: ``` User object internals: OFF SZ TYPE DESCRIPTION 0 8 (object header) # Mark Word + 型指针(压缩后4字节) 8 4 int User.age 12 1 boolean User.flag 13 3 (alignment/padding gap) # 对齐填充 16 4 String User.name # 引用型 Instance size: 20 bytes # 总大小需对齐到8字节,实际占用24字节 ``` --- #### 关键点 1. **锁升级与对象头** 锁状态变化(无锁 → 偏向锁 → 轻量级锁 → 重量级锁)会修改Mark Word的结构[^2]。 2. **指针压缩优化** 通过`-XX:+UseCompressedOops`开启,减少引用型和型指针的内存占用[^1]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值