写在前面
本文看下计算对象大小相关内容。
1:基础内容
1.1:对象的结构
一个对象由对象头和对象体组成,其中对象头包含如下内容:
标记字(mark word):存放GC年龄信息,对象锁信息等,占用8个字节
class指针:指向方法区中的class文件信息,占用4个字节(指针压缩)
数组长度信息(array length):数组特有,记录数组的长度,int表示,所以是8个字节,该项非数组可忽略
对象体存储的是具体的对象内容以及内部padding:
对象内容:具体的属性信息
内部padding:如果不是8字节的整数倍,则填充为8字节的整数倍,因为CPU是以8byte为单位来获取数据的
当对象头和对象体的总大小不是8byte的整数倍时需要通过外部aligment来填充到整数倍,整体结构如下:
因此就算是一个空对象其大小也至少是标记字8字节+class指针4字节+对象内容0字节+外部alignment4字节=16字节
。如下定义一个类:
public class EmptyObject {
}
然后创建100个对象实例,如下:
public class Main {
public static void main(String[] args) throws InterruptedException {
List<EmptyObject> oneHundredList = new ArrayList<>();
for (int i = 0; i < 100; i++) {
oneHundredList.add(new EmptyObject());
}
Thread.sleep(Integer.MAX_VALUE);
}
}
如下查看:
C:\Users\Administrator>jps -l
7984 org.jetbrains.jps.cmdline.Launcher
12692
20196 sun.tools.jps.Jps
3512 dongshi.daddy.objectsize.Main
14892
19580 org.jetbrains.idea.maven.server.RemoteMavenServer36
C:\Users\Administrator>jmap -histo 3512 > d:\\test\\emptyobj100.txt
可以看到每个是16字节,当我们定义一个int属性,大小是多少呢?依然是16字节,因为有了4字节的int就不要4字节的外部alignment了,如下:
public class EmptyObject {
int num;
}
重复执行创建100个对象实例的程序:
C:\Users\Administrator>jps -l
11780 sun.tools.jps.Jps
12692
12568 dongshi.daddy.objectsize.Main
14892
19356 org.jetbrains.jps.cmdline.Launcher
19580 org.jetbrains.idea.maven.server.RemoteMavenServer36
C:\Users\Administrator>jmap -histo 12568 > d:\\test\\withint.txt
那如果我们再添加一个short成员变量,此时一个EmptyObject占用多大内存呢?答案是24字节,计算如下:
对象头标记字:8字节
对象头class指针:4字节
对象内容int:4字节
对象内容short:2字节
内部padding:2字节
此时总共20字节,不是8整倍数,所以需要外部alignment4字节
所以总共是24字节
修改如下:
public class EmptyObject {
int num;
short shortNum;
}
重新生成100个对象实例后查看:
C:\Users\Administrator>jmap -histo 3952 | findstr ":EmptyObject"
C:\Users\Administrator>jmap -histo 3952 | findstr "EmptyObject"
28: 100 2400 dongshi.daddy.objectsize.EmptyObject
可以看到确实是24字节。
2:包装类型和原生类型
先说结论,包装数据类型要比原生类型占用更多的内存,因为多了对象头和填充的内存占用,分别来看下Integer和Long。
2.1:Integer
对于int,其占用4个字节,但是如果是其包装类型Integer,则要占用(对象头标记字8字节+对象头class指针4字节+对象内容int本身4字节=16字节),如下也可以验证:
C:\Users\Administrator>jmap -histo 3952 | findstr "java.lang.Integer"
19: 258 4128 java.lang.Integer
总大小4128除以对象个数258等于16字节。
2.2:Long
对于long,其占用8个字节,但是如果是其包装类型Long,则要占用(对象头标记字8字节+对象头class指针4字节+对象内容int本身8字节+外部alignment4字节=24字节),如下也可以验证:
C:\Users\Administrator>jmap -histo 7212 | findstr "java.lang.Long"
28: 100 2400 java.lang.Long
可以看到每个的大小是2400/100=24字节
。
所以在实际编码中,能使用基本数据类型的还是使用基本数据类型,因为包装数据类型的内存占用量相比于基础数据类型要多出几倍。
3:数组
以int数组为例,对于一维的int数组,其结构如下:
因此如果是int[256]则大小是(标记字8字节+class指针4字节+数组长度4字节+内容256*4=1040字节)
对于二维数组int[dim1][dim2]而言,每个int[dim2]都是使用一个额外的对象来表示的,因此会占用更大的内存空间,如果我们将int[256]使用int[128][2]来表示的话,则结构如下图:
每个元素的大小是(指针4字节+int[2]对象24字节=28字节),共128个,所以大小是128*28=3584字节,再加上第一纬的标记头8字节,class指针4字节,数组长度4字节,共3600字节,可看到存储相同量的元素,int[128][2]比int[256]多占用了(3600-1040=2560字节)的内存空间。
所以,在实际工作中,尽量避免使用多维数组,有需要也尽量使用一维数组来实现,将一维数组折叠一下就行了。
4:String
想要分析处String占用的内存大小,需要先来看下String的定义(只列出会占用堆内存的)
:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
}
结构如下:
则一个空string占用的大小是36字节,如果是不包括String对象头的话,则大小是24字节。
5:普通的类
如下的类:
public class EmptyObject {
int a;
byte b;
Integer c = new Integer(10);
}
内存结构如下:
所以总大小是(标记字8字节+class指针4字节+int a 4字节+byte b 1字节+Integer c指针 4字节=21字节),再对齐,因此是24字节,如下验证:
public class Main {
public static void main(String[] args) throws InterruptedException {
List<EmptyObject> oneHundredList = new ArrayList<>();
for (int i = 0; i < 100; i++) {
oneHundredList.add(new EmptyObject());
}
Thread.sleep(Integer.MAX_VALUE);
}
}
:\Users\Administrator>jps -l
13200 dongshi.daddy.objectsize.Main
12692
6824 sun.tools.jps.Jps
14892
19580 org.jetbrains.idea.maven.server.RemoteMavenServer36
3308 org.jetbrains.jps.cmdline.Launcher
C:\Users\Administrator>jmap -histo 13200 | findstr "EmptyOb"
28: 100 2400 dongshi.daddy.objectsize.EmptyObject
可以看到每个确实是24字节。