以编程方式计算Java对象占用的内存,包括它引用的对象

我需要以编程方式确切地找出给定Java对象占用的内存量,包括它引用的对象所占用的内存。

我可以使用工具生成内存堆转储并分析结果。 但是,生成堆转储需要花费大量时间,并且需要这样的工具来读取转储以生成报告。 鉴于我可能需要多次这样做,如果我可以在我的项目中输入一些能够给我这个值“运行时”的代码,那么我的工作会更灵活。

我怎么能最好地实现这个目标?

ps:具体来说,我有一个javax.xml.transform.Templates类型的对象集合

你需要使用reflection 。 由此产生的代码对我来说太复杂了(尽管它很快将作为我正在构建的GPL工具包的一部分提供),但主要的想法是:

  • 对象头使用8个字节(用于类指针和引用计数)
  • 每个原始字段使用1,2,4或8个字节,具体取决于实际类型
  • 每个对象引用(即非原始)字段使用4个字节(引用,加上引用的对象使用的任何内容)

您需要单独处理数组(标头的8个字节,长度字段的4个字节,表的4 *长度字节,以及内部使用的任何对象)。 您需要使用reflection处理迭代字段(以及它的父字段)的其他类型的对象。

您还需要在递归期间保留一组“看到”的对象,以便不要多次计算在多个位置引用的对象。

看起来已经有一个名为Classmexer的实用工具。

我自己没有尝试过,但在推出自己的路线之前,我会走那条路。

一个好的通用解决方案是使用堆大小增量。 这涉及最小的努力并且可在任何类型的对象/对象图之间重复使用。 通过多次实例化和销毁对象并在其间进行垃圾收集,然后取平均值,可以避免编译器和JVM优化,从而改变结果并获得相当准确的结果。 如果您需要一个完整的答案,那么这可能不是您的解决方案,但对于我所知道的所有实际应用程序(分析,内存需求计算),它的效果非常好。 下面的代码就是这样做的。

public class Sizeof { public static void main(String[] args) throws Exception { // "warm up" all classes/methods that we are going to use: runGC(); usedMemory(); // array to keep strong references to allocated objects: final int count = 10000; // 10000 or so is enough for small ojects Object[] objects = new Object[count]; long heap1 = 0; // allocate count+1 objects, discard the first one: for (int i = -1; i < count; ++i) { Object object; //// INSTANTIATE YOUR DATA HERE AND ASSIGN IT TO 'object': object=YOUR OBJECT; ////end your code here if (i >= 0) { objects[i] = object; } else { object = null; // discard the "warmup" object runGC(); heap1 = usedMemory(); // take a "before" heap snapshot } } runGC(); long heap2 = usedMemory(); // take an "after" heap snapshot: final int size = Math.round(((float)(heap2 - heap1)) / count); System.out.println("'before' heap: " + heap1 + ", 'after' heap: " + heap2); System.out.println("heap delta: " + (heap2 - heap1) + ", {" + objects[0].getClass() + "} size = " + size + " bytes"); } // a helper method for creating Strings of desired length // and avoiding getting tricked by String interning: public static String createString(final int length) { final char[] result = new char[length]; for (int i = 0; i < length; ++i) { result[i] = (char)i; } return new String(result); } // this is our way of requesting garbage collection to be run: // [how aggressive it is depends on the JVM to a large degree, but // it is almost always better than a single Runtime.gc() call] private static void runGC() throws Exception { // for whatever reason it helps to call Runtime.gc() // using several method calls: for (int r = 0; r < 4; ++r) { _runGC(); } } private static void _runGC() throws Exception { long usedMem1 = usedMemory(), usedMem2 = Long.MAX_VALUE; for (int i = 0; (usedMem1 < usedMem2) && (i < 1000); ++i) { s_runtime.runFinalization(); s_runtime.gc(); Thread.currentThread().yield(); usedMem2 = usedMem1; usedMem1 = usedMemory(); } } private static long usedMemory() { return s_runtime.totalMemory() - s_runtime.freeMemory(); } private static final Runtime s_runtime = Runtime.getRuntime(); } // end of class