Bootstrap

【超详细】Java各种类型所占用的内存空间究竟多大?


准备工作

不喜欢被蒙在鼓里的感觉,鉴于网上大神们说法不一致,我决定亲自实验,探究究竟Java各个类型占用空间情况。

  • 实验环境为 jdk1.8 hotspot虚拟机+win10系统
  • 使用Jprofiler进行探究(IDEA插件直接下载)
    在这里插入图片描述
  • 另外还需要下载jprofile.exe,下载地址,破解方法百度既能找到!
  • 关于jprofiler的具体使用先参考:这篇文章,具体之后再总结。
  • 关于对象的内存布局,以及JVM的压缩指针,之前记录过了,具体看【这篇文章】即可!

对象内存占用情况

  • 已知:Java对象的内存布局包括:1.对象头;2.实例数据;3.补充数据;

  • 在jdk6之后的版本中,指针压缩是被默认开启的,开启指针压缩,对象头占用12bytes(其中markword 8字节,Klasspointer 4字节),不开启指针压缩则对象头占用16bytes(其中markword 8字节,Klasspointer 8字节)。【如果为数组还会存储数组长度,上面链接指向的文章中都有记录】

  • 至于实例数据,原生类型(primitive type)的内存占用如下:
    下面图片是我在别处截的,这个图存在一些问题。。这也是为什么自己想亲自写博客输出的原因之一,不喜欢被蒙在鼓里不清不楚的感觉。。
    boolean在作为数组的时候会被编译器翻译成bytes[ ],所以只占一个字节,但是在单独作为实例变量的时候,boolean会被编译器翻译成int,占4个字节,所以具体多大还得具体分析!
    在这里插入图片描述

  • 至于实例数据中的,对象引用:开启指针压缩的内存优化,对象引用占用4字节,不开启的话占用8字节。

  • 补充数据,是将当前的对象大小向上按照8字节的倍数进行补充,其原因上文连接中也介绍了。


使用Jprofiler进行验证

【注意使用Jprofile查看内存,不能让主线程立马结束,不然查看不了内存占用状况,需要写个sleep】

  • 写个例子测试一下,下面是一个Wang的类,其中是一些实例变量
class Wang {
    private String name;
    private int age;
    private int[] arr = {1, 2, 3};
    private boolean status;
}

我们写个main方法进行测试一下这个对象占用了多少个字节

public static void main(String[] args) throws InterruptedException {
    Wang wang = new Wang("wangz7",18,true);
    Thread.sleep(600 * 1000);
    System.out.println(wang);
}

如图:我们发现这个对象占用了32个字节
推测:对象头12字节 + String4字节 + int4字节 + arr数组引用4字节 + boolean4字节 + 6字节的补充数据
在这里插入图片描述

【关于Retained Size的意思】

  • 指的是:该对象指向别的对象,导致别的对象称为可达对象,那么Retained大小就包括自身的对象大小 + 指向的对象大小。

  • 在这里wang对象持有了两个引用(String和int[ ]),说明这个String对象加上数组共占用64-32=32字节大小

引用关系如图所示
在这里插入图片描述


基本类型的包装类型内存占用

是否开启指针压缩开启关闭
Byte, Boolean16 bytes24 bytes
Short, Character16 bytes24 bytes
Integer, Float16 bytes24 bytes
Long, Double24 bytes24 bytes

可以这么理解。只要是关闭指针压缩,那么一个包装对象在堆中就占用24字节空间
如果开启了指针压缩,那么除了双精度double以及Long这两个基本类型占8字节的包装类型,堆中占用还是24字节,其他的基本类型对应的包装类型均占16字节。


数组占用内存空间

探究这个问题我们可以用到这个工具:openjdk.jol
先添加依赖:

<dependency>
  <groupId>org.openjdk.jol</groupId>
  <artifactId>jol-core</artifactId>
  <version>0.14</version>
</dependency>

然后可以参考我这个例子【这里写了个对象数组,并打印其对象内存占用情况】

class Wang {
    private String name;
    private int age;
    private int[] arr = {1, 2, 3};
    private boolean status;

    public Wang(String name, int age, boolean status) {
        this.name = name;
        this.age = age;
        this.status = status;
    }
}

public class Solution {
    public static void main(String[] args) throws InterruptedException {
        System.out.println(VM.current().details());
        Wang[] wang = new Wang[10];
        System.out.println(ClassLayout.parseInstance(new Wang[10]).toPrintable());
    }
}

输出:
在这里插入图片描述

  • 在64位机器上,开启压缩指针的情况下,对象头中会使用4个字节来描述数组长度,如上图(如果未开启,那么会通过8个字节进行存储数组长度
  • 因此对象头开启指针压缩占16个字节,不开启指针压缩占16+8=24字节。
  • 注意一下,数组占用内存空间就不算对象中的一个个实例对象了,而是只保存引用,所以实例数据部大小 = 数组长度 * 引用4字节
  • 从上图倒数第二行可以知道,这个wang数组对象共占用56字节,推导一下(对象头占用16字节 + 数组长度10 * 引用4字节 = 56 字节!
  • 下图为使用Jprofiler查看wang数组的大小,可以看到是56字节
    在这里插入图片描述- 为了严谨点实验,我们将数组增大到11的长度再试试其大小是否大了?

在这里插入图片描述- 用jol打印发现确实跟上面描述的一样,刚好64字节

在这里插入图片描述
在这里插入图片描述

  • Integer[10]数组同样是56字节(对象头16 + 数组长度 * Integer引用类型4字节 = 56字节)

在这里插入图片描述- int[10]同样也是56字节(但是虽然占用内存相等,但是原因不同。这里注意是int类型,是长度 * int4字节,而不是上面的Integer引用类型占4字节)

在这里插入图片描述


String 对象内存占用空间

  • 首先注意!String对象计算内存的时候有一些不同!因为String底层其实是使用数组实现的,但String又不完全由数组构成,还有成员变量hash值…因此计算String对象占用内存的时候需要既考虑到String对象本身,又要考虑到char[ ]的占用大小,并相加!
  • 考虑到JDK1.9对String底层数据结构进行了优化(将char[ ]数组优化成了Byte[ ]数组),我们先分析JDK1.7到JDK1.8 String底层是char[ ]数组的情况!
  • 补充一个知识点:就是static变量和方法是不与对象进行绑定的,因此使用他们的时候可以不用将对象new出来,直接通过他们的Class模板调用即可,因此他们的引用不是存在对象的实例数据中!而是和类的信息绑定在一起(注意如果是引用对象的话,引用和Class模板绑定,对象依旧在堆中。静态变量在JDK1.7后也均移到了堆中存储,而并非方法区
  • 我们来看看String类的源码:
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    private final char value[];
    
    private int hash; 

    private static final long serialVersionUID = -6849794470754667710L;

    private static final ObjectStreamField[] serialPersistentFields =
        new ObjectStreamField[0];
	....省略...
  • 可以看到除了一个char数组和一个int值,其他的是static修饰的不占用对象的内存。

  • 因此一个String对象在开启指针压缩的64位机上,内存大小是(12字节对象头 + char数组引用4字节 + int4字节 + 4字节填充数据 = 24字节)
    在这里插入图片描述在这里插入图片描述- 如果关闭了指针压缩的优化方案那么(16字节对象头 + 8字节数组引用 + 4字节int + 4字节补充数据 = 32字节)
    在这里插入图片描述

  • 之后再来计算char[ ]数组大小;毕竟String对象有数组的成分,在JDK1.7到JDK1.9之前是char[ ]。因此大小为(16字节对象头 + 字符串长度 * char占用2字节 + 补充数据),因此上面字符串“wang”的char[]数组部分占用16+4*2 = 24字节的大小

  • 因此!在64位机下默认开启指针压缩,String str = new String(“wang”);这个str字符串对象的大小应该是String部分的24字节 + char数组部分的24字节 = 48字节


总结

  • 稍稍了解这里就够了
  • 本篇文章主要记录了一下各种类型占用的内存大小,以及使用jol和jprofiler证明对象的大小。
  • 关于指针压缩、Retained Size、jprofiler的使用,我会记录下在并把连接放到下面:



————— END —————




;