文章目录
- 前言
- 一、介绍一下对象内存布局
- 二、对象内部各个区域的作用
- 1.mark word 标记词
- 2.Klass pointer 类型指针
- 3.数组长度
- 4.实例数据
- 5.填充数据
- 三、使用工具查看对象头大小
- 四、回收一下前言的题目答案
- 验证一下
- 写在最后
前言
从一道【某Y】的面试题入手,今天分析一下Java对象的内存布局:
问下面这个Java对象在64位虚拟机中占多大的内存?
class Test{
int a;
Integer number;
}
【下文以jdk1.8的hotspot虚拟机为例】
一、介绍一下对象内存布局
一个对象由一下几部分构成
(1)对象头
- 对象头中包含mark word,Klass pointer,如果是数组的话还会存下数组大小。
(2)实例数据
- 存储的是类中的成员变量(静态变量可不存这里哦)
(3)填充数据
- 为了将对象的大小凑整为8的倍数
二、对象内部各个区域的作用
1.mark word 标记词
mark word的作用主要是为了记录对象的锁信息的,像是Synchronized和一些锁操作(如下图)【等书到了,我会另外补全这里的知识】,在64位操作系统下,标记词占64bit;在32位系统下,占32bit;
锁状态+锁操作+CMS过程中用到的标记信息
深入探讨mark word
2.Klass pointer 类型指针
我们知道类的信息和对象是分开存储的,对象存在堆中,而类信息存在方法区(jdk1.8的元空间)中。那么两者之间通过什么进行绑定呢?这个时候Klass Pointer类型指针就发挥了作用!
像是对象的一些信息包括(hash码,对象首地址,GC分代年龄)都存在方法区,JVM要获取一个对象的信息,就可以通过这个Klass pointer从方法区获取。
关于Klass pointer的大小,在32位虚拟机中占32bit。
在64位虚拟机中,可以是32bits也可以是64bits,取决条件是一个叫做指针压缩的优化是否开启
- 未开启指针压缩时,类型指针占用8B (64bit)
- 开启指针压缩情况下,类型指针占用4B (32bit)
具体看这篇文章:指针压缩是什么?
需要注意的是jdk6以后的指针压缩是默认开启的(当然也可以手动关掉,在“指针压缩是什么?”一文中有详细讲解),主要还是一种内存空间的优化策略
3.数组长度
这一部分只有对象是数组的时候,才会存在于对象头部,占用4字节(32bit)空间。
4.实例数据
- 1.基本类型占用的字节为:
java基本类型 | 占用字节 |
byte,boolean | 1字节 |
char,short | 2 |
int,float | 4 |
long,double | 8 |
- 2.引用类型占用的字节为:
对于引用类型占用,如果开启了指针压缩,那么占4字节,如果关闭指针压缩,占8字节。(包括String,等基本类型的包装类型)
- 3.如果该类继承于其他父类:
那就是子类会继承父类的成员变量。父类的成员变量会加到子类中。
有个问题:【父类的private变量,子类能继承但是无权使用,会不会加到子类对象的实例数据中?】
- 4.静态变量?
静态变量并不与堆存在一起,jdk1.7之前是存在方法区中的,自从jdk1.7及之后版本都是存在堆中,但不是存在对象的实例数据中,而是分开存储。【具体是怎么存的等书到了再补充…】
- 5.另外
Jvm在对象的实例数据中并不是按照我们声明的先后,从前到后进行排序的,而是涉及了另一个优化方式,叫做【字段重排序】
5.填充数据
填充数据的作用仅仅是为了:如果上面4部分的数据加起来,如果所占用的字节数不是8的倍数,那么填充数据就进行填充,填充到8的倍数!
【原因】64位的操作系统一次IO也就是64位,也就是8个字节,将对象凑整成8的倍数,每次IO的李利用率就提升了
三、使用工具查看对象头大小
可以通过OpenJDK的 jol 工具将对象的内存布局进行打印
这个工具是可以在Oracle的JDK中打印信息的,放心使用
下面是pom的依赖部分
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.14</version>
</dependency>
使用案例
public class Test {
public static void main(String[] args) {
System.out.println(VM.current().details()); 打印虚拟机当前情况
String wang = "a";
ClassLayout layout = ClassLayout.parseInstance(wang);
System.out.println(layout.toPrintable()); 打印对象内存布局占用详细情况
System.out.println(layout.headerSize()); 打印对象头大小
System.out.println(layout.instanceSize()); 打印对象占用大小
}
}
四、回收一下前言的题目答案
问下面这个Java对象在64位虚拟机中占多大的内存?
class Test{
int a;
Integer number;
}
默认情况下指针压缩开启
mark word 8字节+ klass pointer 4字节 + int 4字节 + Integer引用类型4字节 = 20字节
因为不是8的倍数,因此补充数据4字节凑整
最后Test对象24字节。
验证一下
1. 使用jol
public class Test {
public static void main(String[] args) {
System.out.println(VM.current().details());
Wang wang = new Wang();
ClassLayout layout = ClassLayout.parseInstance(wang);
System.out.println(layout.toPrintable());
}
}
2. 最后输出
默认是开启指针压缩的内存优化,因此占用24个字节。
写在最后
后续会再写一篇文章,专门验证每个区域的内存大小,以及指针压缩的内存优化细节
使用jol查看对象内存占用
————— END —————