对象内存问题

对象的内存布局

对象的内存布局是由 Java 虚拟机规范定义的,但具体的实现细节各有不
同,如 HotSpot 和 OpenJ9 就不一样。

就拿我们常用的 HotSpot 来说吧。
对象在内存中包括三部分:对象头、实例数据和对齐填充。

对象头

对象头是对象存储在内存中的元信息,包含了Mark Word、类型指针等信
息。
Mark Word 存储了对象的运行时状态信息,包括锁、哈希值、GC 标记
等。在 64 位操作系统下占 8 个字节,32 位操作系统下占 4 个字节。
类型指针指向对象所属类的元数据,也就是 Class 对象,用来支持多态、
方法调用等功能。
除此之外,如果对象是数组类型,还会有一个额外的数组长度字段。占 4
个字节。

类型指针会被压缩吗?
类型指针可能会被压缩,以节省内存空间。比如说在开启压缩指针的情况下
占 4 个字节,否则占 8 个字节。在 JDK 8 中,压缩指针默认是开启的。

实例数据

实例数据是对象实际的字段值,也就是成员变量的值,按照字段在类中声
明的顺序存储。JVM 会对这些数据进行对齐/重排,以提高内存访问速度。

对齐填充

由于 JVM 的内存模型要求对象的起始地址是 8 字节对齐(64 位 JVM
中),因此对象的总大小必须是 8 字节的倍数。
如果对象头和实例数据的总长度不是 8 的倍数,JVM 会通过填充额外的
字节来对齐。

CPU 进行内存访问时,一次寻址的指针大小是 8 字节,正好是 L1 缓存
行的大小。如果不进行内存对齐,则可能出现跨缓存行访问,导致额外的缓
存行加载,CPU 的访问效率就会降低。

提问: new Object() 对象的内存大小?

一般来说,目前的操作系统都是 64 位的,并且 JDK 8 中的压缩指针是默
认开启的,因此在 64 位的 JVM 上,new Object()的大小是 16 字节(12
字节的对象头 + 4 字节的对齐填充)。

对象头的大小是固定的,在 32 位 JVM 上是 8 字节,在 64 位 JVM 上是
16 字节;如果开启了压缩指针,就是 12 字节。
实例数据的大小取决于对象的成员变量和它们的类型。对于new
Object()来说,由于默认没有成员变量,因此我们可以认为此时的实例数
据大小是 0。

文章推荐阅读:Object o = new Object()占多少个字节?

JVM访问对象的方法?

主流的方式有两种:句柄和直接指针。HotSpot 虚拟机主要使用直接指针来进行对象访问。

句柄是通过一个中间的句柄表来定位对象的,而直接指针则是通过引用直接指向对象的内存地址。优点是,对象被移动时只需要修改句柄表中的指针,而不需要修改对象引用本身。

在直接指针访问中,引用直接存储对象的内存地址;对象的实例数据和类型信息都存储在堆中固定的内存区域。优点是访问速度更快,因为少了一次句柄的寻址操作。缺点是如果对象在内存中移动,引用需要更新为新的地址。

对象的四种引用?

  1. 强引用
    强引用是 Java 中最常见的引用类型。使用 new 关键字赋值的引用就是强引用,只要强引用关联着对象,垃圾收集器就不会回收这部分对象,即使内存不足。

  2. 软引用

软引用于描述一些非必须对象,通过 SoftReference 类实现。软引用的对
象在内存不足时会被回收。

  1. 弱引用

弱引用用于描述一些短生命周期的非必须对象,如
ThreadLocal中的Entry,就是通WeakReference 类实现的。弱引用的对象会在下一次垃圾回收时会被回收,不论内存是否充足。

  1. 虚引用

虚引用主要用来跟踪对象被垃圾回收的过程,通过 PhantomReference 类实
现。虚引用的对象在任何时候都可能被回收。

对象一定分配在堆中吗?

不一定。
默认情况下,Java 对象是在堆中分配的,但 JVM 会进行逃逸分析,来判断对象的生命周期是否只在方法内部,如果是的话,这个对象可以在栈上分配。

什么是逃逸分析?

逃逸分析是一种 JVM 优化技术,用来分析对象的作用域和生命周期,判断对象是否逃逸出方法或线程。
可以通过分析对象的引用流向,判断对象是否被方法返回、赋值到全局变量、传递到其他线程等,来确定对象是否逃逸。如果对象没有逃逸,就可以进行栈上分配、同步消除、标量替换等优化,以提高程序的性能。

逃逸具体是指什么?

根据对象逃逸的范围,可以分为方法逃逸和线程逃逸。

当对象被方法外部的代码引用,生命周期超出了方法的范围,那么对象就必须分配在堆中,由垃圾收集器管理。

逃逸分析会带来什么好处?

主要有三个。

第一,如果确定一个对象不会逃逸,那么就可以考虑栈上分配,对象占用的内存随着栈帧出栈后销毁,这样一来,垃圾收集的压力就降低很多。

第二,线程同步需要加锁,加锁就要占用系统资源,如果逃逸分析能够确定一个对象不会逃逸出线程,那么这个对象就不用加锁,从而减少线程同步
的开销。

第三,如果对象的字段在方法中独立使用,JVM
可以将对象分解为标量变量,避免对象分配。


对象内存问题
http://bloomivy.github.io/2025/02/05/对象内存问题/
作者
Bloom
发布于
2025年2月5日
许可协议