对象的组成
对象头[Header]
- Markword:存储对象自身运行时数据如hashcode、gc分代年龄等,64位系统总共占用8个字节,关于Markword详细内存分布如下
- 类型指针:对象指向类元数据地址的指针,jdk8默认开启指针压缩,64位系统占4个字节
- 数组长度:若对象不是数组,则不分配空间大小,若是数组,则为4个字节长度
基于上面描述数组和普通对象的对象头有不同的内存大小,主要区别在于数组长度
实例数据[Instance Data]
指的就是对象中各个属性大小,比如User中name和age的内存大小
public class User{
String name;
int age;
}
若开启了类型指针压缩,String是4个字节, int是4个字节,属性填充总共8个字节
内存对齐[Padding]
因为JVM要求java的对象占的内存大小应该是8bit的倍数,所以后面有几个字节用于把对象的大小补齐至8字节的倍数,就不特别介绍了
JOL工具分析对象
Java项目引入依赖
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9</version>
</dependency>
创建对象与结果分析
创建简单无锁对象
public static void main(String[] args) {
ClassLayout classLayout = ClassLayout.parseInstance(new Object());
System.out.println(classLayout.toPrintable());
}
输出结果分析
java.lang.Object 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) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
OFFSET代表内存偏移量,单位是字节
SIZE代表占用内存大小,单位字节
TYPE DESCRIPTION 类型描述,其中object header为对象头
VALUE代表存储值,对应内存中当前存储的值;
对象总大小为16个字节,对象头占用12个字节,前8个字节代表markword,对象运行时数据状态,前64位的value倒序排列拼的markword是16进制为00 00 00 00 00 00 00 01,最后一个字节01的二进制是00000001,最后三位001,代表无锁状态,后4个字节代表对象指向类元数据的指针;空对象没有属性所以实例数据不占内存,对象头+实例数据=12,不是8个倍数,所以补4个字节为16字节
enum { locked_value = 0, // 0 00 轻量级锁
unlocked_value = 1,// 0 01 无锁
monitor_value = 2,// 0 10 重量级锁
marked_value = 3,// 0 11 gc标志
biased_lock_pattern = 5 // 1 01 偏向锁
};
创建有属性的对象
public class ClassLayOutCheck {
public static void main(String[] args) {
ClassLayout classLayout = ClassLayout.parseInstance(new User());
System.out.println(classLayout.toPrintable());
public static class User {
String name;
int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
}
输出结果分析
org.example.object.ClassLayOutCheck$User 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) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 int User.age 1
16 4 java.lang.String User.name (object)
20 4 (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
User对象总大小为24个字节,对象头占用12个字节,前8个字节代表markword,对象运行时数据状态,前8个字节低3位001代表无锁状,后4个字节代表对象指向类元数据的指针;User实例数据是8个字节,有属性name和age,其中name是String类型,不过这里有个问题,显示string的字节长度是4,value显示object,我们都知道内部实现是byte[],其中4字节指代对象内存指针,age是int类型,默认4个字节,value直接显示值;对象头+实例数据=20不是8字节倍数,所以填充4个字节为24个字节
创建数组
public static void main(String[] args) {
ClassLayout classLayoutInt = ClassLayout.parseInstance(new int[]{});
System.out.println("----------------------");
System.out.println(classLayoutInt.toPrintable());
}
[I 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) 6d 01 00 f8 (01101101 00000001 00000000 11111000) (-134217363)
12 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
16 0 int [I.<elements> N/A
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
结果输出分析
对象总大小为16个字节,对象头占用16个字节,前8个字节代表markword,对象运行时数据状态,低2位00代表轻量级锁,其他位是指向栈针中锁记录的指针,4个字节代表对象指向类元数据的指针,最后4字节代表数字的长度;空数组没有属性所以实例数据不占内存,对象头+实例数据=16,是8字节的倍数,不需要填充数据
创建重量级锁对象
public static void main(String[] args) {
User l = new User("cyl",1);
Runnable runnable = () -> {
synchronized (l) {
ClassLayout layout = ClassLayout.parseInstance(l);
System.out.println(layout.toPrintable());
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
};
for (int i = 0; i < 3; i++) {
new Thread(runnable).start();
}
}
public static class User {
String name;
Integer age;
User(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
```
```java
org.example.object.ClassLayOutCheckThread$User object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 5a 1d 04 43 (01011010 00011101 00000100 01000011) (1124343130)
4 4 (object header) ab 7f 00 00 (10101011 01111111 00000000 00000000) (32683)
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 java.lang.String User.name (object)
16 4 java.lang.Integer User.age 1
20 4 (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
org.example.object.ClassLayOutCheckThread$User object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 5a 1d 04 43 (01011010 00011101 00000100 01000011) (1124343130)
4 4 (object header) ab 7f 00 00 (10101011 01111111 00000000 00000000) (32683)
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 java.lang.String User.name (object)
16 4 java.lang.Integer User.age 1
20 4 (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
org.example.object.ClassLayOutCheckThread$User object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 5a 1d 04 43 (01011010 00011101 00000100 01000011) (1124343130)
4 4 (object header) ab 7f 00 00 (10101011 01111111 00000000 00000000) (32683)
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 java.lang.String User.name (object)
16 4 java.lang.Integer User.age 1
20 4 (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
header倒叙最后一个字节是5a,对应二进制是01011010,查看最后三位010,可知对象状态是重量级锁
创建轻量级锁对象
public static void main(String[] args) {
User l = new User("cyl",1);
Runnable runnable = () -> {
synchronized (l) {
ClassLayout layout = ClassLayout.parseInstance(l);
System.out.println(layout.toPrintable());
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
};
for (int i = 0; i < 1; i++) {
new Thread(runnable).start();
}
}
public static class User {
String name;
Integer age;
User(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
输出结果分析
org.example.object.ClassLayOutCheckThread$User object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 00 c9 3f 09 (00000000 11001001 00111111 00001001) (155175168)
4 4 (object header) 00 70 00 00 (00000000 01110000 00000000 00000000) (28672)
8 4 (object header) 43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
12 4 java.lang.String User.name (object)
16 4 java.lang.Integer User.age 1
20 4 (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
header倒叙最后一个字节是00,对应二进制是00000000,查看最后三位000,可知对象状态是轻量级锁
局限性
当对象中嵌套对象时,得出Instance size这是当前对象内存大小,不是所有内存大小,比如set对象,实例属性是map,4个字节表示的对象的引用指针,而不是内部map的实际大小
java.util.HashSet 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) 1c 72 00 f8 (00011100 01110010 00000000 11111000) (-134188516)
12 4 java.util.HashMap HashSet.map (object)
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
可以清楚的看到实例属性java.util.HashMap HashSet.map 的value
是object,4个字节代表它的引用指针。查询实际Java对象中嵌套对象的实际内存大小,可以自己写工具,主要就是递归直到事最基础数据类型,计算出来之后再累加,或者使用已经成熟的工具,参考博客:
两种工具查询Java嵌套对象引用内存大小
参考文章:
1.https://blog.csdn.net/superfjj/article/details/106582678
2.https://blog.51cto.com/u_15485936/5111767
3.https://blog.csdn.net/qq_38824137/article/details/107089862