如何准确计算Java对象的大小
有时,我们需要知道Java对象到底占用多少内存,有人通过连续调用两次System.gc()比较两次gc前后内存的使用量在计算java对象的大小,也有人根据Java虚拟机规范中的Java对象内存排列估算对象的大小,这两种方法或多或少都有问题,因为System.gc()并不一定促发GC,同一个类型的对象在32位与64位JVM中使用的内存会不一样,在64位虚拟机中是否开启指针压缩也会影响Java对象在内存中的大小。
那么有没有一种既准确又方便的方法计算对象的大小呢?答案是肯定的。在Java
5中引入了Instrumentation类,这个类提供了计算对象内存占用量的方法;Hotspot支持instrumentation框架,其他的虚拟机也提供了类似的框架
使用Instrumentation类计算Java对象大小的过程如下:
- 创建一个有premain方法的agent 类,
- JVM在调用agent类的premain方法时会传入一个Instrumentation 对象,调用Instrumentation的getObjectSize方法
- 把agent类打成一个jar包
- 启动我们的应用程序,使用JVM参数指定agent jar的路径
下面以计算Object对象的大小为例,详细介绍使用Instrumentation计算对象大小的过程
1. 创建Instrumentation agent类
Instrumentation
agent类有一个方法premain,声明如下:
1 public static void premain(String args, Instrumentation inst) {
2 ...
3 }
JVM会在应用程序运行之前调用这个方法(也就是在执行应用程序的main方法之前),JVM会在调用该方法时传入一个实现Instrumentation接口的实例,此时我们就可以调用getObjectSize()方法来计算对象的大小。例如我们要计算Object实例和自定义类型MyObject实例的大小,agent代码如下:
1 package my; 2 import java.lang.instrument.Instrumentation; 3 4 5 public class MyAgent { 6 public static void premain(String args, Instrumentation inst) { 7 Object obj = new Object(); 8 System.out.println("Bytes used by Object:"+ inst.getObjectSize(obj)); 9 System.out.println("Bytes used by MyObject:"+ inst.getObjectSize(new MyObject())); 10 } 11 public static void main(String[] args) { 12 System.out.println("main is over"); 13 } 14 }
MyObject代码如下:
1 package my; 2 3 public class MyObject{ 4 Object object = new Object(); 5 }
需要注意的是agent类不需要实现任何接口,只需要定义premain方法就行,JVM会自动调用该方法。
2. 把agent类打包成jar包
在打包之前需要创建manifest
文件,创建manifest.txt文件,包括以下内容:
Premain-Class: my.MyAgent
然后执行一下命令创建jar包
jar -cmf manifest.txt agent.jar my/*
3.使用agent运行应用程序
运行应用程序,并使用javaagent命令行参数指定instrumentation
agent的jar文件,加入classpath为当前目录并且main方法在com.mypackage.Main中,命令如下:
java -javaagent:agent.jar -cp . my.MyAgent
在32位机器上运行结果如下:
hadoop@32bithost:~/workspace/my/bin$ java -javaagent:agent.jar
my.MyAgent
Bytes used by Object:8
Bytes used by MyObject:16
main is
over
在64位机器上(不开启指针压缩)运行结果如下:
[genie.yjd@64bithost ~]$ java -XX:-UseCompressedOops -javaagent:agent.jar -cp . my.MyAgent Bytes used by Object:16 Bytes used by MyObject:24 main is over
在64位机器上(开启指针压缩)运行结果如下:
[genie.yjd@64bithost~]$ java -XX:+UseCompressedOops -javaagent:agent.jar -cp . my.MyAgent
Bytes used by Object:16
Bytes used by MyObject:16
main is over
运行结果显示对于Object对象在32bit机器上占8个字节,在64bit机器上占16个字节,而对于用于一个Object类型成员的MyObject在32bit机器上占用16个字节,而在64bit机器上不开启指针压缩是占用24个字节,开启指针压缩后占用16个字节。
在应用程序中访问Instrumentation对象
在上面的例子中,我们在premain方法中计算对象的大小。可是如果我们想要在应用程序执行期间计算某个对象的大小该怎么办呢?
我们可以这样做,在premain方法中把Instrumentation对象保存在一个static引用中,然后提供一个static方法访问这个实例,代码如下:
1 public class MyAgent { 2 private static volatile Instrumentation globalInstr; 3 public static void premain(String args, Instrumentation inst) { 4 globalInstr = inst; 5 } 6 public static long getObjectSize(Object obj) { 7 if (globalInstr == null) 8 throw new IllegalStateException("Agent not initted"); 9 return globalInstr.getObjectSize(obj); 10 } 11 }
这样我们就可以在应用程序中调用 MyAgent.getObjectSize()来计算运行时任意实例的大小了。
深度计算对象内存使用
注意getObjectSize方法不包括对象应用的其他对象的大小。加入对象A引用对象B,使用getObjectSize()方法计算对象A的大小时,只包括对象B引用的大小(4byte),而不是对象B的真是大小。如何深度计算对象的大小,我会在下一篇blog中详细说明。