很多时候我们想知道一个Java对象到底有多大, 但Java没有像C语言那样提供一个类似于sizeof的方法。作为Java老司机的你,是否考虑过这个问题?
在《深入理解Java虚拟机》这边书中提到HotSpot虚拟机是8字节对齐的, 一个Java对象包括对象头(Header),实例数据(Instance Data)和对齐填充(Padding), 现在的HotSpot虚拟机一般运行在64位电脑上。
不管是否压缩,即UseCompressOops。 Java对象占用字节数都是8的整数倍,8的整数倍,8的整数倍!!! 重要的事情说3遍。
Object obj = new Object(); 请问在这句代码使用了多大内存? 不考虑压缩(XX:-UseCompressedOops),答案是24个字节, 其中引用obj在Java栈里使用8个字节, new Object在Java堆里使用16个字节。
Java Instrumentation类的getObjectToSize方法可以测量Java对象的大小, 要使用javagent注入的方式得到Instrumentation的引用。
/** * Returns an implementation-specific approximation of the amount of storage consumed by * the specified object. The result may include some or all of the object's overhead, * and thus is useful for comparison within an implementation but not between implementations. * * The estimate may change during a single invocation of the JVM. * * @param objectToSize the object to size * @return an implementation-specific approximation of the amount of storage consumed by the specified object * @throws java.lang.NullPointerException if the supplied Object is <code>null</code>. */ long getObjectSize(Object objectToSize);
我总结为表格, 方法理解
Java语言有8种原生数据类型, 即8种类型关键字。原生类型(primitive type)的内存占用如下:
| 类型 | 内存占用(字节) |
| boolean | 1 |
| byte | 1 |
| short | 2 |
| char | 2 |
| int | 4 |
| float | 4 |
| long | 8 |
| double | 8 |
下边是我总结的计算方法:
| 对象大小:(对象头 + 实例数据 + padding)%8等于0, padding是补齐成8的整数倍 下面各个对象的大小都是按照上述顺序计算的。 | |||
| 对象 | 已压缩(字节) -XX:+UseCompressedOops | 未压缩(字节) -XX:-UseCompressedOops | 说明 |
| new Object() | 12+Padding=16 | 16 | 压缩前对象头占16个字节,压缩后占12个字节。 |
| static class A { int a; } new A() | 12+4=16 12是对象头,4是int型大小 | 16+4+Padding=24 16是对象头大小,4是int型大小 | 对象头压缩前占16个字节,压缩后占12个字节;int型占4个字节。 |
| static class B { int a; int b; } new B() | 12+4+4+Padding=24 12是对象头,第一个4是变量a,第二个4是变量b, paddinb是余8补齐的4. | 16+4+4=24 | 区别是已压缩要添加8字节对齐的Padding。 |
| static class B2 { int b2a; Integer b2b; } new B2() | 12+4+4=24 12是对象头,第一个4是变量b2a,第二个4是b2b引用。 | 16+4+8+Padding=32 | 在64位机器上引用压缩前占8个字节,压缩后占4个字节。 |
| new Object[3] | 16+3*4+Padding=32 16是对象头,4是单个引用大小。 | 24+8*3=48 24是对象头,8是单个引用大小 | 数组对象的对象头在压缩前占24个字节,压缩后占16个字节。 |
| new Object[1] | 16+1*4+padding=24 16是对象头,4是引用大小; | 24+1*8=32 24是对象头,8是单个引用大小 | |
| 单一对象头 | 12 | 16 | |
| 数组对象头 | 16 | 24 | |
| 引用 | 4 | 8 | |
有兴趣的可以下载我用 IntelliJ写的demo工程:http://download.youkuaiyun.com/detail/brycegao321/9670233
1、新建Java工程;
2、设置输出为jar包, 得到MANIFEST.MF文件。
选中 Build on make选项。
修改MANIFEST.MF, 注意Boot-Class-Path后面有个空格!!!
MANIFEST.MF要添加下面三行 Premain-class: SizeUtils (实现premain方法,得到Instrumentation的引用) Can-Redefine-Classes: false Boot-Class-Path: (注意:Path:后面有空格,否则编译不过!!!)
编译后生成jar包。
测试代码:
public class SizeTest {
/**
* -XX:+UseCompressedOops: mark/4 + metedata/8 + 4 = 16
* -XX:-UseCompressedOops: mark/8 + metedata/8 + 4 + padding/4 = 24
*/
static class A {
int a;
}
/**
* -XX:+UseCompressedOops: mark/4 + metedata/8 + 4 + 4 + padding/4 = 24
* -XX:-UseCompressedOops: mark/8 + metedata/8 + 4 + 4 = 24
*/
static class B {
int a;
int b;
}
/**
* -XX:+UseCompressedOops: mark/4 + metedata/8 + 4 + 4 + padding/4 = 24
* -XX:-UseCompressedOops: mark/8 + metedata/8 + 8 + 4 + padding/4 = 32
*/
static class B2 {
int b2a;
Integer b2b;
}
/**
* 不考虑对象头:
* 4 + 4 + 4 * 3 + 3 * sizeOf(B)
*/
static class C extends A {
int ba;
B[] as = new B[3];
C() {
for (int i = 0; i < as.length; i++) {
as[i] = new B();
}
}
}
static class D extends B {
int da;
Integer[] di = new Integer[3];
}
/**
* 会算上A的实例字段
*/
static class E extends A {
int ea;
int eb;
}
public static void main(String[] args) throws IllegalAccessException {
System.out.println("sizeOf(new Object())=" + SizeUtils.sizeOf(new Object()));
System.out.println("sizeOf(new A())=" + SizeUtils.sizeOf(new A()));
System.out.println("sizeOf(new B())=" + SizeUtils.sizeOf(new B()));
System.out.println("sizeOf(new B2())=" + SizeUtils.sizeOf(new B2()));
System.out.println("sizeOf(new B[3])=" + SizeUtils.sizeOf(new B[3]));
System.out.println("sizeOf(new C())=" + SizeUtils.sizeOf(new C()));
System.out.println("fullSizeOf(new C())=" + SizeUtils.fullSizeOf(new C()));
System.out.println("sizeOf(new D())=" + SizeUtils.sizeOf(new D()));
System.out.println("fullSizeOf(new D())=" + SizeUtils.fullSizeOf(new D()));
System.out.println("sizeOf(new int[3])=" + SizeUtils.sizeOf(new int[3]));
System.out.println("sizeOf(new Integer(1)=" + SizeUtils.sizeOf(new Integer(1)));
System.out.println("sizeOf(new Integer[0])=" + SizeUtils.sizeOf(new Integer[0]));
System.out.println("sizeOf(new Integer[1])=" + SizeUtils.sizeOf(new Integer[1]));
System.out.println("sizeOf(new Integer[2])=" + SizeUtils.sizeOf(new Integer[2]));
System.out.println("sizeOf(new Integer[3])=" + SizeUtils.sizeOf(new Integer[3]));
System.out.println("sizeOf(new Integer[4])=" + SizeUtils.sizeOf(new Integer[4]));
System.out.println("sizeOf(new A[3])=" + SizeUtils.sizeOf(new A[3]));
System.out.println("sizeOf(new E())=" + SizeUtils.sizeOf(new E()));
System.out.println("sizeOf(new HashMap)=" + SizeUtils.sizeOf(new HashMap<String,Object>()));
}
}输出: 左边是没压缩, 右边是压缩的, 单位是字节。 注意cmd命令啊!
java -javaagent:CalcSize.jar -XX:+UseCompressedOops -jar CalcSize.jar
java -javaagent:CalcSize.jar -XX:-UseCompressedOops -jar CalcSize.jar
参考:
http://www.jroller.com/maxim/entry/again_about_determining_size_of
http://yueyemaitian.iteye.com/blog/2033046
http://www.importnew.com/14948.html
本文探讨了Java中如何确定对象的大小,详细解释了HotSpot虚拟机对象的组成,包括对象头、实例数据和对齐填充,并指出Java对象总是8字节对齐。通过《深入理解Java虚拟机》提及的内容,举例说明一个Object对象在不考虑压缩时占用24字节,其中栈上的引用占8字节,堆上的对象占16字节。还介绍了使用Java Instrumentation的getObjectSize方法来测量对象大小,并提供了一个IntelliJ的demo工程示例,指导读者如何创建和运行测试,以观察不同压缩选项下对象的内存占用情况。

被折叠的 条评论
为什么被折叠?



