因为JVM虚拟机为HotSpot,以下谈的都是在64位HotSpot虚拟机中
对象内存布局
对象的内存布局可以分为三块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)
注:数组对象中对象头部分多了数组长度Length,存储长度为4字节
其内存结构:
- MarkWord:存储对象的hashCode、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等
- ClassPointer:指向对象对应的Class对象(类对象)的内存地址
- InstanceData:具体的数据大小,如对象含有一个int成员变量,即为4字节
MarkWord
在openJDK源码中的 markOop.hpp 文件中看到可以看到下面,markOop.hpp文件一般都在目录 hotspot\src\share\vm\oops 下
32位与64位JVM有一些不同,现在都是使用64位虚拟机
在64位JVM中,MarkWord为8字节-64bit
不同的锁状态MarkWord会不同:
当无锁态时,64bit分别为:
- 25bit不使用
- 31bit hashCode形式的对象的地址
- 再后一位unused是cms_free:不是64位虚拟机就占用 0 bit,是64位就占用 1bit
- Age为GC分代年龄,4bit,所以对象GC年龄最大为15
- biased_lock偏向锁,1bit,0代表无锁,1代表偏向锁
- lock锁信息,2bit,用于记录锁的状态
00—轻量锁
01—偏向锁/无锁,根据前面的biased_lock决定
10—重量锁
11—GC标记
注意:无锁态01是指没有加锁,而下面的无锁00是指CAS锁
ClassPointer
ClassPointer本来也是8字节,但64位JVM中默认压缩ClassPointer
通过cmd中java -XX:+PrintCommandLineFlags -version
其中:
- UseCompressedClassPointers 类指针压缩:压缩class pointer,由8字节压缩至4字节,间接的提高内存的利用率
- UseCompressedOops 普通对象指针压缩:通常64位JVM消耗的内存会比32位的大1.5倍,这是因为对象指针在64位架构下,长度会翻倍(更宽的寻址),通过UseCompressedOops 可以压缩指针
开启UseCompressedOops 也默认强制开启UseCompressedClassPointers,关闭UseCompressedOops 默认关闭UseCompressedClassPointers
JVM优化之压缩普通对象指针(CompressedOops)
其他
- 实例数据(Instance Data)即对象中的包含的属性
Object o = new Object()
Object中Instance Data为0
MyObject o = new MyObject(3);
class MyObject{
private int a;
public MyObject(int a) {
this.a = a;
}
}
MyObject中InstanceData为4字节(int型数据为4字节)
- Padding:将对象内存填充为8的倍数
为什么对象内存要为8的倍数?
目的是alignment,它允许以一些空间为代价加快内存访问。如果数据未对齐,则处理器需要在加载内存后进行一些转换才能访问它。
此外,垃圾回收简化(并加快)最小分配单元的大小
如果您有32位引用,则可以寻址最多2^32或4 GB的内存(实际上,尽管您获得的更少,但更像3.5 GB)。如果您有64位引用,则可以寻址2^64,即内存为terrabytes。但是,对于64位引用,一切都趋于放缓并占用更多空间。这是由于处理64位的32位处理器的开销,以及由于更少的空间和更多垃圾收集而导致所有处理器上的GC循环更多。
因此,创作者采取了一个中间立场,并决定使用35位参考,这样可以达到2^35或32 GB的内存,占用更少的空间,从而获得与32位参考相同的性能优势。这是通过读取一个32位参考并在读取时将其左移3位来完成的,并且在存储参考时将其右移3位。这意味着所有对象必须在2^3边界(8字节)上对齐。这些被称为压缩的普通对象指针或压缩的oops。
为什么不用36位引用来访问64 GB内存?那么,这是一个折衷。您需要大量浪费空间来处理16字节的对齐,并且据我所知,绝大多数处理器不会从16字节对齐中获得速度优势,而不是8字节对齐。
请注意,JVM不会打扰使用压缩的oops,除非最大内存设置为4 GB以上,而默认情况下它不是。实际上,您可以使用-XX:+UsedCompressedOops标志启用它们。
这是在32位虚拟机当天重新提供64位系统上的额外可用内存。据我所知,64位虚拟机没有限制。
来源:Java性能:权威指南,第8章
- 数组对象还有一个Length段:4字节
通过JOL检验
Maven导入依赖
<!--JOL 内存布局工具-->
<!-- https://mvnrepository.com/artifact/org.openjdk.jol/jol-core -->
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.14</version>
<scope>provided</scope>
</dependency>
测试类:
public class T01 {
public static void main(String[] args) {
//MyObject o = new MyObject(3);
//int[] o = new int[4];
Object o = new Object();
System.out.println(ClassLayout.parseInstance(o).toPrintable());
synchronized (o){
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
}
}
class MyObject{
private int a;
public MyObject(int a) {
this.a = a;
}
}
一共16字节,其中前8字节为MarkWord,8-12字节为ClassPointer,12-16为Padding填充为8的倍数
synchronized 锁信息就添加在MarkWord
MyObject o = new MyObject(3);
InstanceData为int型数据-4字节,即8+4+4=16,不需要填充
int[] o = new int[4];
数组对象Object Header多了一段Length=4,因为InstanceData为空,也是16字节