“Java的基本类型有:byte、char、short、int、float、double、long、boolean8种数据类型,其中前面几种数据类型分别占用:1、2、2、4、4、8、8个byte。唯独boolean类型没有定义占用多大内存,那么本篇就通过一些工具来验证boolean占用的内存大小,顺便介绍如何计算对象在堆中的占用多大内存。
”
验证boolean类型内存大小
首先说结论:存在局部变量的boolean类型变量占用4个字节,存在堆内存中占用1个字节。
在《Java编程思想》这本书中,对其他7种基本数据类型都定义了占用的内存大小,唯独boolean类型没有定义,并且还说了一句:boolean类型所占存储空间的大小没有明确指定,仅定义为能够取字面值true或false。
Java的基本数据类型不像C/C++为了可移植而导致同一个数据类型在不同的机器(32位、64位机器)有不同的内存占用,因此C/C++提供了sizeof()函数计算数据类型占用多大内存。而Java的就没有这个顾虑,同一个数据类型在不同的机器上占用的空间是一样的,因此就没有类似sizeof()函数。
说明这点后,以下的实验结果就是普遍性的,无论你手头上的机器是32位还是64位,Java的boolean类型占用的内存都是一样的。
局部变量占用4个字节
public class Student {
public void test01(){
boolean a = false;
}
}
在test01()方法体中定义了一个局部变量a,并赋值为false,我们看看编译后生成的字节码文件
0 iconst_0 # 将数字0压入操作数栈中
1 istore_1 # 操作数栈弹栈,弹出的0存入本地变量表slot为1的位置
2 return # 方法结束返回
对于iconst_0、istore_1不太清楚的朋友可以看指令说明:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.iconst_i
在指令说明中,我们可以得知false对应的是int类型的0,true对应的是1。由此可知在方法调用的过程中,栈帧中的boolean类型占用了4个字节。
堆中占用1个字节
验证这类情况的步骤稍微麻烦一点,此处需要借助Mat这类dump文件查看工具。测试代码如下
public class TestOOM {
public static void main(String[] args) {
List<Student> list = new ArrayList<>();
// 死循环向list添加数据
while (true) {
Thread.sleep(100);
list.add(new Student());
}
}
}
// 定义Student类
class Student {
boolean flag1 = true;
}
启动Java程序后先jps定位到TestOOM进程,然后jmap命令导出dump文件
之后在Mat中打开oom2.hprof文件
可以看到List集合中存在了大量的Student对象,并且每个对象的浅堆占用了16个字节,深堆也是占用了16个字节。浅堆大小是我们分析的关键,因为它表示了一个对象本身占用大小,不包含对象中引用类型的成员变量占用的大小。至于16个字节如何计算出来的,请往下看。
我们先看一下Student对象在堆中的内存布局:
由于每个对象占用的内存空间必须是8的倍数,也就是说浅堆大小不是16就是24、32、40......以此往后加8。而JVM开启指针压缩后(-XX:+UseCompressedOops 默认开启)对象头总共占用12个字节,在Mat中看到的16个字节中有12个字节被对象头占用。
那么还剩下4个字节是被boolean类型的flag和对齐填充padding占用了。
为了验证flag在堆中的student对象占用了几个字节,我们修改一下Student类:
class Student {
boolean flag1 = true;
boolean flag2 = true;
boolean flag3 = true;
boolean flag4 = true;
}
在Student类中多增加3个boolean类型的成员变量。我们先假设boolean类型占用1个字节,以上4个boolean类型就占用了4个字节,对象头占用12个字节,因此对象总共占用16个字节(因为16已经是8的倍数了,此时对齐填充占用0个字节即可)。重复刚刚的dump文件的步骤,在Mat中查看Student对象的浅堆大小:
可以看到跟上述分析的一模一样。为了再次证明boolean类型占用1个字节,我们再次改造一下Student类
class Student {
boolean flag1 = true;
boolean flag2 = true;
boolean flag3 = true;
boolean flag4 = true;
boolean flag5 = true;
}
此时一个Student对象中成员变量就需要占用5个字节,而对象头同样还是12个字节,因此需要17个字节的内存空间,可是17不是8的倍数啊,因此需要对齐填充7位,所以总共需要24个字节。
重复以上步骤dump出hprof文件后查看此时的student大小:
可以看到堆中student大小确实是24个字节。
小结一波:
由上述两个实验也就证明了boolean类型在JVM不同的内存空间中占用的内存大小是不一样的,在栈帧中以局部变量的方式存在就占用4个字节,在堆中就占用1个字节即可。
堆空间中对象占用大小的计算
由上述我们其实已经知道如何计算浅堆的大小了。我们一般问一个对象的大小只需回答浅堆大小即可,如果被问到GC回收一个对象可以释放多少空间那就是深堆的大小。接下来通过一个例子掌握计算对象的内存占用大小
class Student {
String str;
byte aByte;
Object object;
long aLong;
int anInt;
}
同样以Student对象作为例子:对象头占用12个字节,引用类型占用4个字节,因此str、object各占用4个字节,aByte占用1个字节,aLong占用8个字节,anInt占用4个字节,最后相加:12+4+1+4+8+4 = 34,由于34不是8的倍数需要对齐填充至40,因此一个Student需要占用40个byte的内存空间。
总结:
本篇重点说明了Java语言中boolean基本类型内存的占用情况,同样也介绍了对象的内存计算方式。但是文中还有两个点本应该解释清楚的,为了控制篇幅就没有继续展开:1,为什么Java对象内存占用大小必须是8的倍数。2,为什么必须内存对齐。这两个点以后有时间会再写一篇说明原因,当然,这里有个链接,是从计算机底层说明内存对齐的作用,请欣赏~