文章目录

  • 前言
  • 一、介绍一下对象内存布局
  • 二、对象内部各个区域的作用
  • 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的倍数


java 对象都是在堆中 java对象在jvm中的存储_System



二、对象内部各个区域的作用

1.mark word 标记词

mark word的作用主要是为了记录对象的锁信息的,像是Synchronized和一些锁操作(如下图)【等书到了,我会另外补全这里的知识】,在64位操作系统下,标记词占64bit;在32位系统下,占32bit;

java 对象都是在堆中 java对象在jvm中的存储_System_02

锁状态+锁操作+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 对象都是在堆中 java对象在jvm中的存储_java_03


四、回收一下前言的题目答案

问下面这个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. 最后输出

java 对象都是在堆中 java对象在jvm中的存储_System_04

默认是开启指针压缩的内存优化,因此占用24个字节。


写在最后

后续会再写一篇文章,专门验证每个区域的内存大小,以及指针压缩的内存优化细节

使用jol查看对象内存占用




————— END —————