HotSpot VM 中对象的内存分析
16lz
2021-01-22
对象的内存布局
HotSpot VM 中,对象在堆内存中的存储布局分为:
对象头(Header)
实例数据(Instance Data)
对齐填充(Padding)
1、对象头
- Mark Word,对象运行时数据,动态定义位数据。包含 HashCode、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等。32 位和 64 位的 VM 里 Mark Word 的长度分别 32 bit 和 64 bit。
- 类型指针,对象指向所属 class 的元数据的指针,32 位 VM 4 byte,64 位 VM 8 byte(-XX:+UseCompressedOops 压缩指针 4 byte)。非必需,取决 JVM 实现。
- 数组对象必须包含长度信息,4 byte。
2、实例数据
- 按分配策略和源码定义的顺序,存储对象的字段数据。
- 8 byte:long、double
- 4 byte:int、float、oops(Ordinary Object Pointers)
- 2 byte:short、char
- 1 byte:byte
- 1 bit:boolean
3、对齐填充
- 非必需,保证对象大小是 8 字节的整数倍进行填充的长度。
- 没有特殊含义,仅占位。
对象的创建
new 语法创建普通 Java 对象的过程
- 检查类是否已加载、解析、初始化,若没有则进行。
- 为新对象分配内存。规整的内存使用指针碰撞(Bump The Pointer);不规整的内存使用列表记录可用内存的方式配分叫空闲列表(Free List)。
- 配分内存的两种安全方式:CAS;每个线程在 Heap 中预先分配一小块内存,即本地线程分配缓存(Thread Local Allocation Buffer)。
- 初始化分配的内存,字段设为默认零值
- 根据 JVM 运行的情况,设置对象头信息。如类的元数据、HashCode、GC 分代年龄、偏向锁...
- 执行构造函数、字段赋值...
对象的访问定位
通过栈上的 reference 数据定位、访问堆上的对象,两种方式:
- 句柄访问。栈中本地变量表里的 reference 存储的是堆中句柄池中对象的句柄地址,句柄包含了对象实例数据和类型数据的地址。
- 直接指针。栈中本地变量表里的 reference 存储的是对象数据地址,可以直接访问,少一次寻址访问速度快。
分析一把
那么下面这个字符串对象占用多大内存?
new String("ConstXiong");
- 假设是 JVM 是 64 位的 HotSpot VM,JDK 8,单纯看这个 String 对象的内存大小,不看常量池,有类型指针,默认开启指针压缩。
- String 类的静态变量存储在类的 Class 对象中,不计算;父类和接口都不包含普通对象字段,所以只需关注下面源码。
JDK 8 String 源码
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
}
Mark Word 64 bit = 8 byte
类型指针 4 byte
实例数据 value 变量,4 byte
实例数据 hash,是 int,4 byte
对齐 4 byte
共 24 字节。使用 openjdk-jol 工具打印内存布局如下:
java.lang.String object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) d0 16 40 17 (11010000 00010110 01000000 00010111) (390076112)
12 4 char[] String.value [C, o, n, s, t, X, i, o, n, g]
16 4 int String.hash 0
20 4 (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
补充一下,实例数据 value 变量对应的 char[] 对象 = ['C','o','n','s','t','X','i','o','n','g']),数组对象比普通对象对象头多了一个 int 记录数组长度,64 bit(Mark Word) + 4 byte(类型指针) + 4 byte(数组长度) + 10 * 2 byte(10个字符) + 0 byte(补齐) = 40 byte
[C object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 08 02 14 17 (00001000 00000010 00010100 00010111) (387187208)
12 4 (object header) 0a 00 00 00 (00001010 00000000 00000000 00000000) (10)
16 20 char [C.<elements> N/A
36 4 (loss due to the next object alignment)
Instance size: 40 bytes
code:
package constxiong;
import org.openjdk.jol.info.ClassLayout;
public class TestObjectSize {
public static void main(String[] args) {
System.out.println(ClassLayout.parseInstance(new String("ConstXiong")).toPrintable());
System.out.println(ClassLayout.parseInstance(new char[]{'C', 'o', 'n', 's', 't', 'X', 'i', 'o', 'n', 'g'}).toPrintable());
}
}
maven pom:
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.10</version>
</dependency>