1.对象创建
Java运行程序过程中创建对象——通过new关键字实现。虚拟机遇到一条new指令时,首先在常量池中定位类的符号引用,检查这个符号引用代表的类是否已经被加载,解析和初始化。如果没有则执行类加载机制。
类加载完成后,虚拟机将new 出的对象分配内存, 从Java堆中划分出一块确定大小的内存。
2. 对象内存分布
这个在无数面试题中被提到了很多次,但都往往只是留于书面形式的理解,这次我使用OpenJDK提供的Java虚拟机对象布局分析工具:JOL 来实操一遍。
首先添加maven依赖:
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.14</version>
</dependency>
接着分别对Integer,String,list类型进行测试,看在Java对象究竟是怎样在Java堆中进行内存分配的。
@Test
public void integerTest() {
Integer integer = 200;
//对象内部布局
System.out.println(ClassLayout.parseInstance(integer).toPrintable());
System.out.println("---------------------");
//对象的引用图
System.out.println(GraphLayout.parseInstance(integer).toPrintable());
}
Integer类型对象内存分配情况
java.lang.Integer object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) be 22 00 20 (10111110 00100010 00000000 00100000) (536879806)
12 4 int Integer.value 200
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
---------------------
java.lang.Integer@39c0f4ad object externals:
ADDRESS SIZE TYPE PATH VALUE
6b820e7c0 16 java.lang.Integer 200
Addresses are stable after 1 tries.
可以看到内部编码 object header 对象头为12字节,实例数据占4字节, Instance size为16字节。
String类型内存占用情况
@Test
public void stringTest() {
String str = "demo test string";
//对象内部布局
System.out.println(ClassLayout.parseInstance(str).toPrintable());
System.out.println("---------------------");
//对象的引用图
System.out.println(GraphLayout.parseInstance(str).toPrintable());
}
采用四个字符组成的String字符串
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) da 02 00 20 (11011010 00000010 00000000 00100000) (536871642)
12 4 char[] String.value [d, e, m, o]
16 4 int String.hash 0
20 4 (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
---------------------
java.lang.String@3c09711bd object externals:
ADDRESS SIZE TYPE PATH VALUE
d6feb330 24 java.lang.String (object)
d6feb348 24 [C.value [d, e, m, o]
可以看出内部编码字符串对象占 24 个字节,其中头部占 12 个字节,实例数据占 8 个字节(value 字符数组 4 个字节,int 类型 hash 属性占 4 个字节),另外有 4 个字节的对齐填充(loss due to the next object alignment),共计 24 个字节。
外部编码有String的24字节和String 中内容默认的24个字节
List集合的内存占用情况
@Test
public void listTest() {
List<String> list = new ArrayList<>();
for (int i = 0; i < 3; i++) {
list.add(String.valueOf(i));
}
//对象内部布局
System.out.println(ClassLayout.parseInstance(list).toPrintable());
System.out.println("---------------------");
//对象的引用图
System.out.println(GraphLayout.parseInstance(list).toPrintable());
//总字节数
System.out.println(GraphLayout.parseInstance(list).totalSize());
}
这里添加三个String类型的值到集合中
java.util.ArrayList object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 7e 2f 00 20 (01111110 00101111 00000000 00100000) (536883070)
12 4 int AbstractList.modCount 3
16 4 int ArrayList.size 3
20 4 java.lang.Object[] ArrayList.elementData [(object), (object), (object), null, null, null, null, null, null, null]
Instance size: 24 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
---------------------
java.util.ArrayList@4590c9c3d object externals:
ADDRESS SIZE TYPE PATH VALUE
d6feb380 24 java.util.ArrayList (object)
d6feb398 24 [C .elementData[0].value [0]
d6feb3b0 24 java.lang.String .elementData[0] (object)
d6feb3c8 56 [Ljava.lang.Object; .elementData [(object), (object), (object), null, null, null, null, null, null, null]
d6feb400 24 [C .elementData[1].value [1]
d6feb418 24 java.lang.String .elementData[1] (object)
d6feb430 24 [C .elementData[2].value [2]
d6feb448 24 java.lang.String .elementData[2] (object)
224
这里添加的是String类型的数字,其中ArrayList内部编码占24个字节,这个是固定不变的。
外部编码有 24个字节的ArrayList,每个集合序列中的0, 1, 2 和其value值分别占24个字节,再加上56个字节存储ArrayList, 其中每一个Object占10个字节,每个null占2个字节。(不满8个字节进行自动补全)
单独一个Object对象占用情况
@Test
public void listObject() {
Object object = new Object();
//对象内部布局
System.out.println(ClassLayout.parseInstance(object).toPrintable());
System.out.println("---------------------");
//对象的引用图
System.out.println(GraphLayout.parseInstance(object).toPrintable());
}
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) e5 01 00 20 (11100101 00000001 00000000 00100000) (536871397)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
---------------------
java.lang.Object@337d0578d object externals:
ADDRESS SIZE TYPE PATH VALUE
d6fed190 16 java.lang.Object (object)
一目了然,一个单纯的Object对象只占用3个对象头的12个字节 + 补全的4个字节 = 16个字节。
自定义对象占用内存
@Test
public void listObjectByOwn() {
ActivityChannel activityChannel = new ActivityChannel();
//对象内部布局
System.out.println(ClassLayout.parseInstance(activityChannel).toPrintable());
System.out.println("---------------------");
//对象的引用图
System.out.println(GraphLayout.parseInstance(activityChannel).toPrintable());
}
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 73 32 01 20 (01110011 00110010 00000001 00100000) (536949363)
12 4 java.lang.Long ActivityChannel.id null
16 4 java.lang.Long ActivityChannel.activityInfoId null
20 4 java.lang.String ActivityChannel.qrCode null
24 4 java.lang.String ActivityChannel.shortUrl null
28 4 java.lang.Integer ActivityChannel.channel null
32 4 java.lang.String ActivityChannel.shareToken null
36 4 (loss due to the next object alignment)
Instance size: 40 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
---------------------
com.example.test.bean.ActivityChannel@5cc7c2a6d object externals:
ADDRESS SIZE TYPE PATH VALUE
d6ff3ea0 40 com.example.test.bean.ActivityChannel (object)
一个自定义的ActivityChannel对象,在不进行赋值的情况下占用内存就是内部编码所占用的内存,即每一个字段占4个字节。
终极:集合中添加自定义对象并进行存储所占内存
@Test
public void listTest() {
ActivityChannel activityChannel = new ActivityChannel(100L,200L,"faf", "fsgs", 4, "4314134");
List<ActivityChannel> list = new ArrayList<>(5);
list.add(activityChannel);
ActivityChannel activityChannel2 = new ActivityChannel(100L,200L,"faf", "fsgs", 4, "3141512");
list.add(activityChannel2);
ActivityChannel activityChannel3 = new ActivityChannel(200L,300L,"faf", "fsgs", 4, "3141512");
list.add(activityChannel3);
//对象内部布局
System.out.println(ClassLayout.parseInstance(list).toPrintable());
System.out.println("---------------------");
//对象的引用图
System.out.println(GraphLayout.parseInstance(list).toPrintable());
//总字节数
System.out.println(GraphLayout.parseInstance(list).totalSize());
}
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 7e 2f 00 20 (01111110 00101111 00000000 00100000) (536883070)
12 4 int AbstractList.modCount 3
16 4 int ArrayList.size 3
20 4 java.lang.Object[] ArrayList.elementData [(object), (object), (object), null, null]
Instance size: 24 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
---------------------
java.util.ArrayList@6574b225d object externals:
ADDRESS SIZE TYPE PATH VALUE
d6184d40 16 java.lang.Integer .elementData[2].channel 4
d6184d50 5948928 (something else) (somewhere else) (something else)
d6731350 24 java.lang.Long .elementData[1].id 100
d6731368 9561336 (something else) (somewhere else) (something else)
d704f860 40 com.example.test.bean.ActivityChannel .elementData[0] (object)
d704f888 24 java.lang.Long .elementData[0].activityInfoId 200
d704f8a0 24 java.lang.String .elementData[2].qrCode (object)
d704f8b8 24 [C .elementData[2].qrCode.value [f, a, f]
d704f8d0 24 java.lang.String .elementData[2].shortUrl (object)
d704f8e8 24 [C .elementData[2].shortUrl.value [f, s, g, s]
d704f900 24 java.lang.String .elementData[0].shareToken (object)
d704f918 32 [C .elementData[0].shareToken.value [4, 3, 1, 4, 1, 3, 4]
d704f938 24 java.util.ArrayList (object)
d704f950 40 [Ljava.lang.Object; .elementData [(object), (object), (object), null, null]
d704f978 40 com.example.test.bean.ActivityChannel .elementData[1] (object)
d704f9a0 24 java.lang.Long .elementData[1].activityInfoId 200
d704f9b8 24 java.lang.String .elementData[2].shareToken (object)
d704f9d0 32 [C .elementData[2].shareToken.value [3, 1, 4, 1, 5, 1, 2]
d704f9f0 40 com.example.test.bean.ActivityChannel .elementData[2] (object)
d704fa18 24 java.lang.Long .elementData[2].id 200
d704fa30 24 java.lang.Long .elementData[2].activityInfoId 300
Addresses are stable after 1 tries.
528
集合中添加自定义对象会在集合中对于相同值进行覆盖,这样内存中只需要存储集合序列中不同的值即可,更进一步减少内存的占用。
添加3个ActivityChannel对象的list集合只用了528个字节。