准备工作
不喜欢被蒙在鼓里的感觉,鉴于网上大神们说法不一致,我决定亲自实验,探究究竟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, Boolean | 16 bytes | 24 bytes |
Short, Character | 16 bytes | 24 bytes |
Integer, Float | 16 bytes | 24 bytes |
Long, Double | 24 bytes | 24 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 —————