从一道【某Y】的面试题入手,今天分析一下Java对象的内存布局:
问下面这个Java对象在64位虚拟机中占多大的内存?
class Test{ int a; Integer number; }
【下文以jdk1.8的hotspot虚拟机为例】
一个对象由一下几部分构成
(1)对象头
(2)实例数据
(3)填充数据
mark word的作用主要是为了记录对象的锁信息的,像是Synchronized和一些锁操作(如下图)【等书到了,我会另外补全这里的知识】,在64位操作系统下,标记词占64bit;在32位系统下,占32bit;
锁状态+锁操作+CMS过程中用到的标记信息
深入探讨mark word
我们知道类的信息和对象是分开存储的,对象存在堆中,而类信息存在方法区(jdk1.8的元空间)中。那么两者之间通过什么进行绑定呢?这个时候Klass Pointer类型指针就发挥了作用!
像是对象的一些信息包括(hash码,对象首地址,GC分代年龄)都存在方法区,JVM要获取一个对象的信息,就可以通过这个Klass pointer从方法区获取。
关于Klass pointer的大小,在32位虚拟机中占32bit。
在64位虚拟机中,可以是32bits也可以是64bits,取决条件是一个叫做指针压缩的优化是否开启
具体看这篇文章:指针压缩是什么?
需要注意的是jdk6以后的指针压缩是默认开启的(当然也可以手动关掉,在“指针压缩是什么?”一文中有详细讲解),主要还是一种内存空间的优化策略
这一部分只有对象是数组的时候,才会存在于对象头部,占用4字节(32bit)空间。
java基本类型 | 占用字节 |
---|---|
byte,boolean | 1字节 |
char,short | 2 |
int,float | 4 |
long,double | 8 |
对于引用类型占用,如果开启了指针压缩,那么占4字节,如果关闭指针压缩,占8字节。(包括String,等基本类型的包装类型)
那就是子类会继承父类的成员变量。父类的成员变量会加到子类中。
有个问题:【父类的private变量,子类能继承但是无权使用,会不会加到子类对象的实例数据中?】
静态变量并不与堆存在一起,jdk1.7之前是存在方法区中的,自从jdk1.7及之后版本都是存在堆中,但不是存在对象的实例数据中,而是分开存储。【具体是怎么存的等书到了再补充…】
Jvm在对象的实例数据中并不是按照我们声明的先后,从前到后进行排序的,而是涉及了另一个优化方式,叫做【字段重排序】
填充数据的作用仅仅是为了:如果上面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()); 打印对象占用大小 } }
————— END —————