Java中如何获取对象大小?使用Instrumentation代码遇空指针异常
你遇到的空指针异常,根源其实很明确:instrumentation这个静态变量根本没被初始化!
为啥会这样?因为premain方法是Java Agent的专属入口——只有当你把这个类作为Java Agent启动时,JVM才会在应用程序启动前调用它,传入合法的Instrumentation实例。但你直接运行C类的main方法时,premain完全不会被执行,instrumentation自然还是null,调用getObjectSize时可不就触发空指针了嘛。
下面给你两种靠谱的解决思路:
方法一:正确使用Java Agent(推荐,准确获取堆内存大小)
要让premain被调用,你需要把代码打包成Java Agent Jar,并在启动时指定这个Agent:
编写MANIFEST.MF文件:
创建一个名为MANIFEST.MF的文件,内容如下:Premain-Class: ObjectSizeFetcher Can-Redefine-Classes: true这个文件告诉JVM,这个Jar是Java Agent,入口类是
ObjectSizeFetcher。打包成Jar:
用jar命令把编译后的class文件和MANIFEST.MF打包:jar cvfm ObjectSizeFetcher.jar MANIFEST.MF ObjectSizeFetcher.class C.class带着Agent运行程序:
启动时通过-javaagent参数指定Agent Jar:java -javaagent:ObjectSizeFetcher.jar C这时候JVM会先执行
premain初始化instrumentation,之后调用getObjectSize就不会有空指针了。
方法二:不用Agent的替代方案(适合快速估算)
如果不想折腾Java Agent,也可以用这些方法,但要注意它们的局限性:
1. 序列化估算大小
用序列化后的字节数来粗略估算对象大小,比如用Apache Commons Lang的工具类:
import org.apache.commons.lang3.SerializationUtils; public class ObjectSizeFetcher { public static long getSerializedSize(Object o) { return SerializationUtils.serialize(o).length; } }
⚠️ 注意:这个值是对象序列化后的字节数,和堆中真实大小有差异——序列化会额外添加格式信息,而且不会计算共享引用的对象大小,适合简单场景的粗略估算。
2. 使用Unsafe(不推荐,兼容性差)
通过sun.misc.Unsafe直接读取JVM内存布局来获取大小,但这是JVM内部API,不同版本/厂商的JVM可能不兼容:
import sun.misc.Unsafe; import java.lang.reflect.Field; public class ObjectSizeFetcher { private static final Unsafe unsafe; static { try { Field field = Unsafe.class.getDeclaredField("theUnsafe"); field.setAccessible(true); unsafe = (Unsafe) field.get(null); } catch (Exception e) { throw new RuntimeException(e); } } public static long getObjectSize(Object o) { return unsafe.getAddress(unsafe.getInt(o, 4L) + 12L); } }
⚠️ 警告:Java 9+对Unsafe的访问做了限制,而且不同JVM的对象内存布局可能不同,生产环境尽量别用这个方法。
总结一下:如果需要准确的堆内存大小,一定要用Java Agent的方式;如果只是粗略估算,序列化方法更稳妥;Unsafe方法只适合你对JVM内存布局非常熟悉的场景。
内容的提问来源于stack exchange,提问作者Sandeep Kumar

