java 一个对象内存有多大
为什么想知道这个,自以为很重要,其实 just for fun =。=
测试相关说明
-
jdk: java 官网上下载的,HotSpot 虚拟机
java version "1.8.0_121" Java(TM) SE Runtime Environment (build 1.8.0_121-b13) Java HotSpot(TM) 64-Bit Server VM (build 25.121-b13, mixed mode)
- 使用的方法: Instrumentation 参考的是 一个对象占用多少字节?
-
确认编译时是 32 位还是 64 位,对测试结果有影响。x86 为 32 位, amd64 为 64 位,其余我不知道
System.out.println(System.getProperty("os.arch"));
准备工作
- SizeOfObject.java 直接从上面链接上拷贝
- 编译这个类,得到 .class 文件
-
在包名路径下执行一下命令进行打包(注意修改相应的包名):
jar cvfm SizeOfObject.jar manifest.mf org/seal_de/SizeOfObject.class
- 其中 manifest.mf 清单信息为: PreMain-Class: org.seal_de.SizeOfObject
- PS: 如果在打包好的 jar 包中,META-INF/MANIFEST.MF 没有 PreMain-Class 属性,添加上去即可
-
在运行测试程序的时候,添加 vm 参数
-javaagent:{jar包路径}\SizeOfObject.jar
测试用例
- 测试 int, Object, 引用的大小。其余类型测试都类似
public class MemoryTest {
/**
* -javaagent:{jar包路径}\SizeOfObject.jar -XX:+UseCompressedOops
* 使用指针压缩,在一定情况下64位HotSpot jvm默认指针压缩
*
*Output:
*amd64
*Object: 16
*
*include one int: 16
*include two int: 24
*include three int: 24
*
*include one object: 16
*include one object: 24
*/
static void test1() {
System.out.println(System.getProperty("os.arch"));
System.out.printf("%-30s%9d\n", "Object:", SizeOfObject.sizeOf(new Object()));
System.out.println();
System.out.printf("%-30s%9d\n", "include one int:", SizeOfObject.sizeOf(new IntegerTestOne()));
System.out.printf("%-30s%9d\n", "include two int:", SizeOfObject.sizeOf(new IntegerTestTwo()));
System.out.printf("%-30s%9d\n", "include three int:", SizeOfObject.sizeOf(new IntegerTestThree()));
System.out.println();
System.out.printf("%-30s%9d\n", "include one object:", SizeOfObject.sizeOf(new ReferenceTestOne()));
System.out.printf("%-30s%9d\n", "include one object:", SizeOfObject.sizeOf(new ReferenceTestTwo()));
}
/**
* -javaagent:{jar包路径}\SizeOfObject.jar -XX:-UseCompressedOops
* 不使用指针压缩
*
*Output:
*amd64
*Object: 16
*
*include one int: 24
*include two int: 24
*include three int: 32
*
*include one object: 24
*include one object: 32
*/
static void test2() {
System.out.println(System.getProperty("os.arch"));
System.out.printf("%-30s%9d\n", "Object:", SizeOfObject.sizeOf(new Object()));
System.out.println();
System.out.printf("%-30s%9d\n", "include one int:", SizeOfObject.sizeOf(new IntegerTestOne()));
System.out.printf("%-30s%9d\n", "include two int:", SizeOfObject.sizeOf(new IntegerTestTwo()));
System.out.printf("%-30s%9d\n", "include three int:", SizeOfObject.sizeOf(new IntegerTestThree()));
System.out.println();
System.out.printf("%-30s%9d\n", "include one object:", SizeOfObject.sizeOf(new ReferenceTestOne()));
System.out.printf("%-30s%9d\n", "include one object:", SizeOfObject.sizeOf(new ReferenceTestTwo()));
}
public static void main(String[] args) {
test2();
}
static class IntegerTestOne {
private int i1 = 1;
}
static class IntegerTestTwo {
private int i1 = 1;
private int i2 = 1;
}
static class IntegerTestThree {
private int i1 = 1;
private int i2 = 1;
private int i3 = 1;
}
static class ReferenceTestOne {
private Object o1 = new Object();
}
static class ReferenceTestTwo {
private Object o1 = new Object();
private Object o2 = new Object();
}
}
一些概念
- 对象内存 = 对象头 + 类型指针 + 对齐填充
- 对象头不参与指针压缩,并且 32 位时为 4 个字节,64 位时为 8 个字节
- 类型指针参与指针压缩,并且 32 位时为 4 个字节,64 位时为 8 个字节;指针压缩时 64 位为 4 个字节
- 对齐填充,由于 jvm 设计内存要为 8 字节的整数倍,所以不足的需要填充。如 对象头和类型指针一共 12 字节,填充后为 16 字节,填充了 4 个字节
测试结果验证上面的假设
- 其中 (8 + 8) 为对象头和类型指针的字节数
64 位 | -XX:-UseCompressedOops | -XX:+UseCompressedOops |
---|---|---|
Object | 8 + 8 = 16 (对象头+类型指针) | 8 + 4 + 4 = 16(对象头+压缩的类型指针+对齐填充) |
包含一个int | (8 + 8) + 4 + 4 = 24 | (8 + 4) + 4 = 16 |
包含两个int | (8 + 8) + 4*2 = 24 | (8 + 4) + 4*2 + 4 = 24 |
包含三个int | (8 + 8) + 4*3 + 4 = 32 | (8 + 4) + 4*3 = 24 |
不压缩引用占 8 个字节,压缩占 4 个字节 | ||
包含一个引用 | (8 + 8) + 8 = 24 | (8 + 4) + 4 = 16 |
包含两个引用 | (8 + 8) + 8*2 = 32 | (8 + 4) + 4*2 + 4 = 24 |
静态数据不在对象内存里面
public class MemoryStaticTest {
/**
* -javaagent:{jar包路径}\SizeOfObject.jar
* 使用指针压缩,在一定情况下64位HotSpot jvm默认指针压缩
*
* Output:
* amd64 +UseCompressedOops
* StaticTest: 16
* Integer: 16
* StaticReferenceTest: 16
*
* @param args
*/
public static void main(String[] args) {
System.out.println(System.getProperty("os.arch") + " +UseCompressedOops");
System.out.printf("%-30s%9d\n", "StaticTest:", SizeOfObject.sizeOf(new StaticTest()));
System.out.printf("%-30s%9d\n", "Integer:", SizeOfObject.sizeOf(new Integer(1)));
System.out.printf("%-30s%9d\n", "StaticReferenceTest:", SizeOfObject.sizeOf(new StaticReferenceTest()));
}
static class StaticTest {
private static int i1 = 1;
private static int i2 = 1;
private static int i3 = 1;
private static int i4 = 1;
private static int i5 = 1;
private static int i6 = 1;
}
static class StaticReferenceTest {
private static Object o1 = new Object();
private static Object o2 = new Object();
private static Object o3 = new Object();
private static Object o4 = new Object();
private static Object o5 = new Object();
}
}
- 代码中还有一点其他测试: https://github.com/Deeeeeeeee/jcoding.git
为什么是这个样子呢
- 对象头在 64 位的时候,为什么是 8 个字节;32 位的时候,为什么是 4 个字节
-
一个引用在 64 位的时候,为什么是 8 个字节;32 位的时候,为什么是 4 个字节
- 这个跟 C++ 指针有关了,跟寄存器有关了
- 指针压缩又是怎么个回事
这一切都可以在 jvm 的实现中找到答案,也就是看 C++ 代码。只是我还没到这个阶段=。=
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。