前言
在Java的世界,JVM托管了一切(堆外内存除外),那我们在日常如何确定一个对象的大小呢?根据内存模型,一个Java对象所占的大小包括:对象头、实例数据和类型填充,而对象头又包括标记位(8字节,以64位机器为例,下同)、类型指针(未开启压缩占4字节,开启压缩占8字节,可通过 java -XX:+PrintCommandLineFlags -version 来看默认开启了哪些选项)和数组长度(4字节)。
我们知道的Java类型占用的大小,分别是byte和boolean为1字节、short和char为2字节,int和float为4字节,long和double为8字节。下面来介绍如何确定Java对象的大小。
下面就来慢慢看一个对象所占大小。
对象实例
下面列举了几个对象,用来判断对象的大小。
public class MyObject1 {
}
public class MyObject2 {
private int v1; // 4
}
public class MyObject3 {
private int v1; // 4
private int v2; // 4
}
public class MyObject4 {
private int v1; // 4
private int v2; // 4
private String v3; // 4
private boolean v4; // 1
private char v5; // 2
}
public class MyObject5 {
private String v1; // 4
private int v2; // 4
private byte v3; // 1
private byte v4; // 1
private int v5; // 4
private Object v6; // 4
private byte v7; // 1
}
public class MyObject6 {
InnerObject[] v1=new InnerObject[2];
private int v2;
static class InnerObject{
private int v;
}
}
Instrument
public class InstrumentationUtil {
private static Instrumentation instrumentation;
public static void premain(String agentArgs, Instrumentation i){
System.out.println("获取到Instrumentation:"+i);
instrumentation=i;
}
public static Instrumentation getInstrumentation(){
return instrumentation;
}
}
public class InstrumentationTest {
public static void main(String[] args) {
Instrumentation instrumentation=InstrumentationUtil.getInstrumentation();
/**
* 开启压缩:-XX:+UseCompressedOops(默认开启,因为java -XX:+PrintCommandLineFlags -version 有输出 )
* 输出:16,8b(mark)+4b(class)+4b(padding)=16b,类型填充为8的倍数,所以是16
*
* 关闭压缩:-XX:-UseCompressedOops
* 输出:16,8b(mark)+8b(class)=16b
*/
System.out.println(instrumentation.getObjectSize(new MyObject1()));
/**
* 开启压缩:-XX:+UseCompressedOops
* 输出:16,8b(mark)+4b(class)+4b(int)=16b
*
* 关闭压缩:-XX:-UseCompressedOops
* 输出:24,8b(mark)+8b(class)+4b(int)+4b(padding)=24b
*/
System.out.println(instrumentation.getObjectSize(new MyObject2()));
/**
* 开启压缩:-XX:+UseCompressedOops
* 输出:24,8b(mark)+4b(class)+4b(int)+4b(int)+4b(padding)=24b
*
* 关闭压缩:-XX:-UseCompressedOops
* 输出:24,8b(mark)+8b(class)+4b(int)+4b(int)=24b
*/
System.out.println(instrumentation.getObjectSize(new MyObject3()));
/**
* 开启压缩:-XX:+UseCompressedOops
* HotSpot创建的对象的字段会先按照给定顺序排列一下,默认的顺序如下,从长到短排列,引用排最后: long/double -> int/float -> short/char -> byte/boolean -> Reference,所以为:
* 输出:32,8b(mark)+4b(class)+4b(int)+4b(int)+2b(char)+1b(boolean)+1b(padding)+4b(String为4b)+4b(padding)=32b,中间的对齐是因为每8个字节都要对齐,而不是在最后才对齐
*
* 关闭压缩:-XX:-UseCompressedOops
* 输出:40,8b(mark)+8b(class)+4b(int)+4b(int)+2b(char)+1b(boolean)+5(padding)+8b(String)=40b,中间的对齐是因为每8个字节都要对齐,而不是在最后才对齐,关闭压缩后String引用变为8字节
*/
System.out.println(instrumentation.getObjectSize(new MyObject4()));
/**
* 开启压缩:-XX:+UseCompressedOops
* 输出:32,8b(mark)+4b(class)+4b(int)+4b(int)+1b(byte)+1b(byte)+1b(byte)+1b(padding)+4b(String引用)+4b(Object)=32b
*
* 关闭压缩:-XX:-UseCompressedOops
* 输出:48,8b(mark)+8b(class)+4b(int)+4b(int)+1b(byte)+1b(byte)+1b(byte)+5b(对齐)+8b(String)+8b(Object引用)=48b,关闭压缩后Object引用变为8字节
*/
System.out.println(instrumentation.getObjectSize(new MyObject5()));
/**
* 开启压缩:-XX:+UseCompressedOops
* 输出:24,8b(mark)+4b(class)+4b(int)+4b(InnerObject[]引用)+4b(padding)=24b
*
* 关闭压缩:-XX:-UseCompressedOops
* 输出:32,8b(mark)+8b(class)+4b(int)+4b(padding)+8b(InnerObject[]引用)=36b
*/
System.out.println(instrumentation.getObjectSize(new MyObject6()));
}
}
Unsafe
public class UnsafeTest {
public static void main(String[] args) throws Exception{
Unsafe unsafe=getUnsafe();
// []
getAllFieldOffset(MyObject1.class,unsafe);
// [v1:int:12],8B+4B=12B
getAllFieldOffset(MyObject2.class,unsafe);
// [v1:int:12,v2:int:16],8B+4B=12B
getAllFieldOffset(MyObject3.class,unsafe);
// [v1:int:12,v2:int:16,v3:String:24,v4:boolean:22,v5:char:20],8B+4B=12B
// 排序为:[v1:int:12,v2:int:16,v5:char:20,v4:boolean:22,v3:String:24],注意v4之后有1B的对齐,所以v3偏移才是24
getAllFieldOffset(MyObject4.class,unsafe);
// [v1:String:24,v2:int:12,v3:byte:20,v4:byte:21,v5:int:16,v6:java.lang.Object:28,v7:byte:22],8B+4B=12B
// 排序为:[v2:int:12,v5:int:16,v3:byte:20,v4:byte:21,v7:byte:22,v1:String:24,v6:java.lang.Object:28],注意v7之后有1B的对齐,所以v1偏移才是24
getAllFieldOffset(MyObject5.class,unsafe);
// [v1:[LMyObject6$InnerObject:16,v2:int:12],8B+4B=12B
// 排序为:[v1:[v2:int:12,LMyObject6$InnerObject:16],InnerObject数组只是个引用,所以只有4字节
getAllFieldOffset(MyObject6.class,unsafe);
}
public static void getAllFieldOffset(Class<?> clz,Unsafe unsafe){
StringJoiner stringJoiner=new StringJoiner(",","[","]");
Field[] fields=clz.getDeclaredFields();
for (Field field:fields){
stringJoiner.add(field.getName()+":"+field.getType().getName()+":"+unsafe.objectFieldOffset(field));
}
System.out.println(stringJoiner);
}
private static Unsafe getUnsafe() {
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
return (Unsafe) field.get(null);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
Lucene工具类
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-core</artifactId>
<version>4.0.0</version>
</dependency>
public class LuceneTest {
public static void main(String[] args) {
// 计算指定对象及其引用树上的所有对象的综合大小:计算指定对象本身在堆空间的大小:计算指定对象及其引用树上的所有对象的综合大小
// 16:16:16 bytes
MyObject1 myObject1=new MyObject1();
System.out.println(RamUsageEstimator.sizeOf(myObject1)+":"+RamUsageEstimator.shallowSizeOf(myObject1)+":"+RamUsageEstimator.humanSizeOf(myObject1));
// 16:16:16 bytes
MyObject2 myObject2=new MyObject2();
System.out.println(RamUsageEstimator.sizeOf(myObject2)+":"+RamUsageEstimator.shallowSizeOf(myObject2)+":"+RamUsageEstimator.humanSizeOf(myObject2));
// 24:24:24 bytes
MyObject3 myObject3=new MyObject3();
System.out.println(RamUsageEstimator.sizeOf(myObject3)+":"+RamUsageEstimator.shallowSizeOf(myObject3)+":"+RamUsageEstimator.humanSizeOf(myObject3));
// 32:32:32 bytes
MyObject4 myObject4=new MyObject4();
System.out.println(RamUsageEstimator.sizeOf(myObject4)+":"+RamUsageEstimator.shallowSizeOf(myObject4)+":"+RamUsageEstimator.humanSizeOf(myObject4));
// 32:32:32 bytes
MyObject5 myObject5=new MyObject5();
System.out.println(RamUsageEstimator.sizeOf(myObject5)+":"+RamUsageEstimator.shallowSizeOf(myObject5)+":"+RamUsageEstimator.humanSizeOf(myObject5));
// 48:24:48 bytes
// 24:8B+4B+4B(Object为4B)+4B(int为4B)=20B,对齐为24B
// 48:new InnerObject[2]的大小为:8B+4B+4B(数组长度)+4B(InnerObject[0]引用)+4B(InnerObject[1]引用)=24B,加上上面的24B就是48B
MyObject6 myObject6=new MyObject6();
System.out.println(RamUsageEstimator.sizeOf(myObject6)+":"+RamUsageEstimator.shallowSizeOf(myObject6)+":"+RamUsageEstimator.humanSizeOf(myObject6));
}
}
jol
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.16</version>
</dependency>
public class ClassLayoutTest {
public static void main(String[] args) {
/**
* OFF SZ TYPE DESCRIPTION VALUE
* 0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
* 8 4 (object header: class) 0xf800c143
* 12 4 (object alignment gap)
* Instance size: 16 bytes
* Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
*/
System.out.println(ClassLayout.parseInstance(new MyObject1()).toPrintable());
/**
* indi.wlh.javaobjectsize.MyObject2 object internals:
* OFF SZ TYPE DESCRIPTION VALUE
* 0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
* 8 4 (object header: class) 0xf800f295
* 12 4 int MyObject2.v1 0
* Instance size: 16 bytes
* Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
*/
System.out.println(ClassLayout.parseInstance(new MyObject2()).toPrintable());
/**
* indi.wlh.javaobjectsize.MyObject3 object internals:
* OFF SZ TYPE DESCRIPTION VALUE
* 0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
* 8 4 (object header: class) 0xf800f3a3
* 12 4 int MyObject3.v1 0
* 16 4 int MyObject3.v2 0
* 20 4 (object alignment gap)
* Instance size: 24 bytes
* Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
*/
System.out.println(ClassLayout.parseInstance(new MyObject3()).toPrintable());
/**
* indi.wlh.javaobjectsize.MyObject4 object internals:
* OFF SZ TYPE DESCRIPTION VALUE
* 0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
* 8 4 (object header: class) 0xf800f3e1
* 12 4 int MyObject4.v1 0
* 16 4 int MyObject4.v2 0
* 20 2 char MyObject4.v5 �
* 22 1 boolean MyObject4.v4 false
* 23 1 (alignment/padding gap)
* 24 4 java.lang.String MyObject4.v3 null
* 28 4 (object alignment gap)
* Instance size: 32 bytes
* Space losses: 1 bytes internal + 4 bytes external = 5 bytes total
*/
System.out.println(ClassLayout.parseInstance(new MyObject4()).toPrintable());
/**
* indi.wlh.javaobjectsize.MyObject5 object internals:
* OFF SZ TYPE DESCRIPTION VALUE
* 0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
* 8 4 (object header: class) 0xf800f420
* 12 4 int MyObject5.v2 0
* 16 4 int MyObject5.v5 0
* 20 1 byte MyObject5.v3 0
* 21 1 byte MyObject5.v4 0
* 22 1 byte MyObject5.v7 0
* 23 1 (alignment/padding gap)
* 24 4 java.lang.String MyObject5.v1 null
* 28 4 java.lang.Object MyObject5.v6 null
* Instance size: 32 bytes
* Space losses: 1 bytes internal + 0 bytes external = 1 bytes total
*/
System.out.println(ClassLayout.parseInstance(new MyObject5()).toPrintable());
/**
* indi.wlh.javaobjectsize.MyObject6 object internals:
* OFF SZ TYPE DESCRIPTION VALUE
* 0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
* 8 4 (object header: class) 0xf800f45f
* 12 4 int MyObject6.v2 0
* 16 4 indi.wlh.javaobjectsize.MyObject6.InnerObject[] MyObject6.v1 [null, null]
* 20 4 (object alignment gap)
* Instance size: 24 bytes
* Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
*/
System.out.println(ClassLayout.parseInstance(new MyObject6()).toPrintable());
/**
* [Ljava.lang.Object; object internals:
* OFF SZ TYPE DESCRIPTION VALUE
* 0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
* 8 8 (object header: class) 0x000000012b7b8038
* 16 4 (array length) 2
* 16 8 (alignment/padding gap)
* 24 16 java.lang.Object Object;.<elements> N/A
* Instance size: 40 bytes
* Space losses: 8 bytes internal + 0 bytes external = 8 bytes total
*/
System.out.println(ClassLayout.parseInstance(new Object[2]).toPrintable()); // 数组大小值的是自身指向的数组大小,而不是内部变量的数组大小
}
}