在了解对象大小之前,先来复习下基本数据大小
数据类型 | 字节大小 |
---|---|
double | 8 |
long | 8 |
float | 4 |
int | 4 |
short | 2 |
char | 2 |
byte | 1 |
boolean | 1 |
对象大小分析
普通对象实例
数组对象实例
注:数组实例对象中对象头多了一个记录数组长度的int类型对象,占4字节
对象头!
HotSpot虚拟机虚拟机的对象头包括两个部分信息:
markword和klass,第一部分markword,用于储存对象的运行时数据,哈希码(HashCode)、GC年龄分代、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。另一部分klass指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的。
对象头占用空间
######1.在32位系统下,存放Class指针的空间大小是4字节,MarkWord是4字节,对象头为8字节。
2.在64位系统下,存放Class指针的空间大小是8字节,MarkWord是8字节,对象头为16字节。
3.在64位开启指针压缩的情况下( -XX:+UseCompressedOops),存放Class指针的空间大小是4字节,MarkWord是8字节,对象头为12字节。
注:开启指针压缩要求内存必须在4GB~32GB,因为32位指针寻址4GB,按8字节对齐,4*8=32GB,按更大对齐可以寻址更大空间,但是浪费就更大了。
4.如果对象是数组,那么另外还要加4字节
注:指针压缩不能压缩markword,指向非堆(Heap)的对象指针,局部变量、传参、返回值、NULL指针
实例数据
实例数据是对象储存的有效信息,也是程序代码中所定义的各种类型的字段内容。无论是从父类继承下来的,还是从子类中定义的,都需要记录起来
对齐填充
最后一块对齐填充空间并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。这是由于HotSpot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说,就是对象的大小必须是8字节的整数倍。
注:对齐填充其实是两次 ,对象的基本数据类型需要一次对齐填充,对象的引用类型也需要一次对齐填充,下面会详细介绍
如何查看对象的大小(64位操作环境)
1.使用jol-core
百度链接:https://pan.baidu.com/s/1bYsh3y6DHcBQbKi6a_FcSQ 提取码:l1nq 。
1.使用ClassLayout.parseInstance(Object instance).toPrintable();将对象大小以表格形式输出
空对象
(此处的空对象是指类中没有任何基础类型和引用,不是对象=null)
import org.junit.Test;
import org.openjdk.jol.info.ClassLayout;
public class MyTest {
@Test
public void test(){
A a = new A();
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
class A{
}
关闭指针压缩
( -XX:-UseCompressedOops)
类A没有值类型和引用类型 对象大小应该为 8(markword)+8(klass) 16byte
运行结果
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) f0 e2 ba 17 (11110000 11100010 10111010 00010111) (398123760)
12 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
Instance size: 16 bytes
开启指针压缩
对象大小应该为 8(markword)+4(klass)因为对象大小最后要能被8整除,所以还要所以还要+4的的填充对齐 ,最后大小还是16byte
运行结果
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) c8 16 01 20 (11001000 00010110 00000001 00100000) (536942280)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
注:64位系统内存大于4GB且小于32GB JVM默认开启指针压缩。
普通对象
import org.junit.Test;
import org.openjdk.jol.info.ClassLayout;
public class MyTest {
@Test
public void test(){
A a = new A();
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
class A{
int a;
float b;
long c;
String d;
}
关闭指针压缩
对象大小应该是 8(markword)+8(Klass)+4(int)+4(float)+8(long)+8(string)(引用指针)40byte
运行结果
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) 58 e3 e9 17 (01011000 11100011 11101001 00010111) (401204056)
12 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
16 8 long A.c 0
24 4 int A.a 0
28 4 float A.b 0.0
32 8 java.lang.String A.d null
Instance size: 40 bytes
开启指针压缩
对象大小应该是 8(markword)+4(Klass)+4(int)+4(float)+8(long)+4(string)(因为开启了指针压缩所以引用指针也是4byte)32byte
运行结果
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) c8 16 01 20 (11001000 00010110 00000001 00100000) (536942280)
12 4 int A.a 0
16 8 long A.c 0
24 4 float A.b 0.0
28 4 java.lang.String A.d null
Instance size: 32 bytes
开启/关闭指针压缩的结果区别:
主要区别就是让原本占用8字节的指针缩小到4字节,另外未开启指针压缩时,上面提到的基本类型内存填充将会以8对齐,开启时以4字节对齐。但是对象尾部的填充不管是否开启都是以8字节对齐。
以下为代码示范:
public class MyTest {
@Test
public void test(){
A a = new A();
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
class A{
int a;
short b;//2byte
float c;
String d;
}
关闭指针压缩
运行结果
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) 58 e3 6b 17 (01011000 11100011 01101011 00010111) (392946520)
12 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
16 4 int A.a 0
20 4 float A.c 0.0
24 2 short A.b 0
26 6 (alignment/padding gap)
32 8 java.lang.String A.d null
Instance size: 40 bytes
可以看到引用类型之前还有一次填充 ,向8补齐
开启指针压缩
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) c8 16 01 20 (11001000 00010110 00000001 00100000) (536942280)
12 4 int A.a 0
16 4 float A.c 0.0
20 2 short A.b 0
22 2 (alignment/padding gap)
24 4 java.lang.String A.d null
28 4 (loss due to the next object alignment)
Instance size: 32 bytes
引用类型之前按4字节对齐 ,对象尾部按8字节对齐
数组对象
注:基础变量数组是对象!!
public class MyTest {
@Test
public void test(){
A[] a = new A[3];
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
class A{
int a;
short b;
String [] d;
}
运行结果
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) 07 17 01 20 (00000111 00010111 00000001 00100000) (536942343)
12 4 (object header) 03 00 00 00 (00000011 00000000 00000000 00000000) (3)
16 12 com.easebuy.test.A A;.<elements> N/A
28 4 (loss due to the next object alignment)
Instance size: 32 bytes
可以看到数组对象头多了一个用于记录数组长度的int型变量,同时引用的对象大小也变成了 4*(数组长度)
用这种方式只可以看到当前对象的大小,它所引用的对象实例大小是不会计算在里面的。如果要计算当前对象和对象引用的所有对象实例可以使用下面介绍的这个方法
2.基于JDK8
JDK1.8有一个类jdk.nashorn.internal.ir.debug.ObjectSizeCalculator
可以评估出对象的大小
public class MyTest {
@Test
public void test(){
A[] a = new A[3];
System.out.println(ObjectSizeCalculator.getObjectSize(a));
}
}
class A{
int a;
short b;
String [] d;
}
运行结果
32
可以看到和上面使用的方法得到的值是一样的,那是因为数组对象只是声明了3个。并没有去实例它们。
下面是实例过的数组对象
public class MyTest {
@Test
public void test(){
A[] a = {new A(),new A(),new A()};
System.out.println(ObjectSizeCalculator.getObjectSize(a));
}
}
class A{
int a;
short b;
String [] d;
}
运行结果
104
可以看到使用这个方法计算对象大小很方便。
总结
“对象在jvm中不是完全连续的,因为存在堆中,还有垃圾回收器的机制影响,总会出现散乱的内存,这就导致了JVM必须为每个对象分配一段内存空间来储存其引用的指针,再结合对象其他必须的元数据,使得对象在持有真实数据的基础上还需要维护额外的数据。”
”在写Java代码需要注意这些JVM内存陷阱。“
参考链接&详细了解
https://www.cnblogs.com/ulysses-you/p/10060463.html
https://www.cnblogs.com/SunDexu/p/3140790.html
https://blog.csdn.net/ignorewho/article/details/80840290
抱怨身处黑暗 不如提灯前行
菅江晖