Java Instrumentation接口实现报错及对象内存估算问题求助
我正在为一个项目估算部分对象的内存,打算用Instrumentation接口的getObjectSize()方法。参考资料后创建Instrumentation Agent,但制作带自定义清单文件的Agent JAR时,出现错误:java.io.FileNotFoundException: META-INF/MANIFEST.MF (No such file or directory)。
执行的完整命令及错误如下:
user@users-MacBook-Pro objectTesting % jar -cfm Sizeof.jar META-INF/MANIFEST.MF Sizeof.class
java.io.FileNotFoundException: META-INF/MANIFEST.MF (No such file or directory)
at java.base/java.io.FileInputStream.open0(Native Method)
at java.base/java.io.FileInputStream.open(FileInputStream.java:219)
at java.base/java.io.FileInputStream.(FileInputStream.java:158)
at java.base/java.io.FileInputStream.(FileInputStream.java:112)
at jdk.jartool/sun.tools.jar.Main.run(Main.java:278)
at jdk.jartool/sun.tools.jar.Main.main(Main.j
我已经单独创建项目文件夹尝试让Instrumentation接口正常工作,最初以为是manifest文件和META-INF文件夹位置错误,但调整多次后仍报错。
解决方案
1. 修复JAR打包的文件找不到错误
这个错误的核心是执行jar命令时,当前目录下不存在META-INF/MANIFEST.MF文件,或者路径写错了。按以下步骤解决:
- 先在执行命令的
objectTesting目录下,手动创建META-INF文件夹,在里面新建MANIFEST.MF文件,内容必须包含Agent的入口配置,比如:
注意:文件最后必须保留空行,否则jar命令会解析失败。Premain-Class: Sizeof Can-Redefine-Classes: true - 确认文件路径正确后再执行打包命令,如果
Sizeof.class在当前目录,命令直接使用即可;如果类文件在子目录(比如target/classes),要补全路径:jar -cfm Sizeof.jar META-INF/MANIFEST.MF ./Sizeof.class
2. 将Instrumentation Agent应用到其他项目
Agent打包成JAR后,有两种方式在其他项目中使用:
- 启动时加载:启动目标Java程序时,添加JVM参数指定Agent JAR的绝对路径:
# 针对可执行JAR java -javaagent:/绝对路径/Sizeof.jar -jar 你的项目.jar # 针对普通主类 java -javaagent:/绝对路径/Sizeof.jar com.你的包名.MainClass - 动态加载:如果目标程序已经在运行,可以通过Attach API动态注入Agent,需要引入
com.sun.tools.attach:attach-api依赖,示例代码:import com.sun.tools.attach.AttachNotSupportedException; import com.sun.tools.attach.VirtualMachine; import java.io.IOException; public class AttachAgent { public static void main(String[] args) throws IOException, AttachNotSupportedException { // 替换为目标进程的PID VirtualMachine vm = VirtualMachine.attach("12345"); vm.loadAgent("/绝对路径/Sizeof.jar"); vm.detach(); } }
3. Java中其他估算对象内存消耗的方法
除了Instrumentation,还有这些常用方法:
- Runtime类差值法:通过创建对象前后的内存差值估算,误差较大(受GC、其他对象影响):
Runtime runtime = Runtime.getRuntime(); long before = runtime.totalMemory() - runtime.freeMemory(); Object obj = new YourObject(); long after = runtime.totalMemory() - runtime.freeMemory(); System.out.println("对象大小:" + (after - before) + "字节"); - 序列化字节法:用工具将对象序列化为字节数组,通过数组长度估算,仅包含可序列化部分,忽略引用:
byte[] bytes = org.apache.commons.lang3.SerializationUtils.serialize(obj); System.out.println("对象大小:" + bytes.length + "字节"); - JOL工具库:直接查看对象内存布局和精确大小,精度很高:
System.out.println(org.openjdk.jol.info.GraphLayout.parseInstance(obj).toPrintable()); - 手动计算:根据JVM内存规则(对象头、实例数据、对齐填充)手动计算,比如64位JVM开启压缩指针时,普通对象头占16字节,加上各字段大小后对齐到8字节倍数。
内容的提问来源于stack exchange,提问作者wdk23

