本文学习一下在不同的环境下,boolean类型的的值在JVM中的内存占用,首先我们借助工具查看Java运行时某些对象占用的内存大小,然后再分析其中的原理。
下面通过gradle引入jol-core,它的全称是Java Object Layout Core,一个用来分析JVM中对象内存布局的工具。
implementation 'org.openjdk.jol:jol-core:0.16'
引入成功之后,先使用JOL打印关于不同类型的对象占用的内存情况,你可以点击下方运行代码
按钮来运行代码和查看输出结果。
import org.openjdk.jol.vm.VM;
public class Main {
public static void main(String[] args) {
System.out.println(VM.current().details());
}
}
如果你本地也是运行在一台64位的计算机,且开启了压缩指针(Compressed OOPs)选项的话,会看到和以上一样的输出,前几行输出的是关于VM的基本信息,下面第一行输出的数字分别表示的是,Java引用类型占4个字节,boolean和byte类型占1个字节,char和short类型占2个字节,int和float类型占4个字节,long和double类型占8个字节,第二行表示的是当以上类型作为数组元素时的占用大小,这里的输出说明当作为数组元素和直接使用占用的内存大小一样。
# Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
在64位环境下,JVM默认开启压缩指针,可通过-XX:-UseCompressedOops参数关闭
注释通过上面的输出我们可以看出,在开启压缩指针的情况下,一个boolean类型占用1个字节,那么如果在没有开启压缩指针的情况下,boolean还是占用一个字节吗?通过设置-XX:-UseCompressedOops再次运行上面的代码,得到的输出最后两行有了变化,如下
# Field sizes by type: 8, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 8, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
在没有开启压缩指针的情况下,Java中的引用类型占用8个字节,比之前多了一倍,但是boolean类型还是占用一个字节,其他基本类型的内存占用也没有变化。
现在确定了boolean类型占用1个字节,那么我们思考一个问题,boolean类型只有true/false两个值,1个bit就能存放,为什么要设计用1个字节来存储boolean类型呢?
在思考这个问题之前,我们先了解一个概念Word Tearing,通俗的解释就是说假如某个处理器不支持单字节写入,那么我们通过直接读取整个字,然后更新其中一个字节,再把整个字写入,这么做就可能会出现问题,这个问题就被称为Word Tearing。不过现在应该没有多少处理器不支持单字节写入了,所以Word Tearing的问题应该很少了,但是现在很多处理器还是不支持单bit写入,如果boolean类型只占一个bit的话,那么在更新它的时候,势必需要读取整个byte,然后更新其中一个bit再写入,这样就会造成类似Word Tearing的问题。
boolean类型是占有1个字节,但是在实际类的使用中,我们多定义一个boolean类型的值,真的只会增加1字节的开销吗?下面定义一个包含boolean类型值的简单类,然后使用JOL打印它们的内存布局情况。
1import org.openjdk.jol.info.ClassLayout;
2
3class BooleanWrapper {
4 private boolean value;
5}
6
7public class Main {
8
9 public static void main(String[] args) {
10 System.out.println(
11 ClassLayout.parseClass(BooleanWrapper.class).toPrintable()
12 );
13 }
14}
从输出中可以看出总共占用16个字节,其中
正因为内存对齐的原因,boolean类型消耗的内存可能往往会1字节多。当然上面这个类就算没有boolean类型的字段,它仍然会占16个字节,它仅仅是说明由于内存对齐的存在,所以boolean类型可能会需要额外消耗。
下面看看boolean数组的内存布局情况。
import org.openjdk.jol.info.ClassLayout;
public class Main {
public static void main(String[] args) {
boolean[] value = new boolean[3];
System.out.println(ClassLayout.parseInstance(value).toPrintable());
}
}
如果运行环境是64位且开启压缩指针的情况下,会得到如下输出
BooleanWrapper object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) N/A
8 4 (object header: class) N/A
12 1 boolean BooleanWrapper.value N/A
13 3 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 3 bytes external = 3 bytes total
从输出中可以看出,除了Mark Word和Klass Pointer,对象头部还多了4个字节,用于记录数组的长度。数组中3个boolean元素占3个字节,末尾5个字节用于内存对齐填充。
至此我们可以总结boolean类型会占用1个字节的内存,但是需要注意的是,由于对象头和内存对齐填充的原因,其真实的开销可能要比预计的多一点。