0、说明
本文各种结论全部参考过标准文献,本人也进行过验证。验证过程本文不做说明。例如:当前主流虚拟机boolean类型运行时确实是1字节。
部分与计算空间无关的细节也不做说明,例如:对象头具体包含哪些信息、分别在哪几位、什么是指针压缩等。
细节信息,本人以后会在《JVM浅析》栏目中一一补充,敬请期待哦~
一、基础知识
1. 数据类型以及大小
基本类型 | |
类型名称 | 占用字节数 |
boolean | 1 |
byte | 1 |
char | 2 |
short | 2 |
int | 4 |
float | 4 |
long | 8 |
double | 8 |
引用类型 | |
操作系统位数 | 占用字节数 |
32位 | 4 |
64位 | 8 (指针压缩后4字节) |
*注:引用类型(注意是引用类型变量,不是对象实例,本质上是指针,其中数组类型变量也属于引用类型变量)。
2. 内存计算公式
对象占用内存 = 对象头开销 + 实例数据(如果是引用类型则包括 变量 和 实例 两部分开销) + 填充数据。
a.对象头开销
指向类引用、垃圾收集信息、同步信息等。
32位JVM 对象头8字节,数组对象头16字节。
64位JVM 对象头16字节(指针压缩后12字节),数组对象头24字节(指针压缩后16字节)。
空对象 / 空数组 都只有对象头。
b.实例数据
基本类型在内存只有值所占用空间大小。
引用类型包含 变量 和 值 两部分占用空间大小(引用变量就相当于指针,用一个系统存储单元存储。值则是堆中实例的大小)。
c.填充数据
hotspot JVM里,对象占用空间是8字节对齐的。因为在内存中一个存储单元是8字节的。
意思是一个Java对象使用的内存一定是8字节的整数倍,如果通过计算后发现对象所需内存不是8字节的整数倍,则会将其填充为8字节的倍数。
例如:对象实例可能需要内存为30字节或者28字节等,都会被填充为32字节。
d.继承关系(不计算父类对象头开销)
子类对象占用内存 = 子类对象头开销 + 子类实例数据 + (父类实例数据+填充数据) + 填充数据。
二、计算实例
因为现在主流都是64位系统,下面测试类会按照64位环境计算内存。并且没有考虑指针压缩的情况。
测试类演示了最复杂的情况,只为演示计算过程,命名不规范,请勿在实际项目中模仿。
new B()总共占用内存空间 = [ (4(a) + 2(bs) + 2(填充数据)) + 4(ba) + 8(cs数组引用变量) + 4(填充数据) ] + [ 24(cs数组对象头) + 3(cs数组内引用变量个数,即cs[0]、cs[1]、cs[2]) * 8(cs数组中引用变量占用内存) ] + [ (16(C实例字节头信息) + 1(c变量) + 7(填充字节)) * 3(C实例数量) ] = 144字节(8字节的整数倍)。
备注
64位JVM才会有指针压缩的情况。-XX:+UseCompressedOops(开启) -XX:-UseCompressedOops(关闭)。
JDK1.6以后才加入此项功能并且默认开启指针压缩。但计算公式不变,只是有些对象占用内存数值会变。大家自行计算压缩指针后或者32位JVM下的占用内存。
查看指针压缩等虚拟机参数信息可用如下两个命令:
jcmd //查看进程信息 (数字列显示的是PID,要保证程序在运行过程中,想测试的话可以到公司服务器上查看)。
jcmd [PID] VM.flags //查看该pid对应进程设置的JVM参数。