【问题标题】:Programmatically calculate memory occupied by a Java Object including objects it references [duplicate]以编程方式计算Java对象占用的内存,包括它引用的对象[重复]
【发布时间】:2010-10-19 22:17:18
【问题描述】:

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

我可以生成内存堆转储并使用工具分析结果。但是,生成堆转储以及此类工具读取转储以生成报告需要相当长的时间。鉴于我可能需要多次执行此操作,如果我可以在我的项目中添加一些代码来赋予我“运行时”这个价值,我的工作将会更加灵活。

我怎样才能最好地做到这一点?

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

【问题讨论】:

  • 您可以在运行时使用反射来调查您的对象图。
  • 实例的开销如何?这也可能因运行时和系统而异(想想 x64...)。
  • 我不会称之为完全的欺骗。
  • 嗯,不完全是骗子,因为原始问题未能提供在这种情况下可用的答案......

标签: java memory-management


【解决方案1】:

您需要为此使用 反射。生成的代码太复杂了,我无法在此处发布(尽管它很快会作为我正在构建的 GPL 工具包的一部分提供),但主要思想是:

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

您需要分别处理数组(8 字节的标头、4 字节的长度字段、4*length 字节的表,以及内部对象使用的任何内容)。您需要使用反射处理遍历字段(及其父字段)的其他类型的对象。

你还需要在递归过程中保留一组“seen”对象,以免多次计算多个地方引用的对象。

【讨论】:

  • 对于它的价值,对象头至少使用了那么多,但我认为较旧的 JVM 使用更多。这也很容易(足够)通过简单的测试代码来验证(创建一百万个对象、gc、睡眠、检查空闲内存——理论上不能保证工作,但实际上可以)。
  • 另外:4 个字节用于 32 位引用。对于 64 位系统,我认为是 8 个字节。
  • 也许 64 位 VM 使用压缩指针……这很有意义。
  • 难道不同的 JVM 在某些方面可能略有不同吗?
  • 是的,我认为这是完全可能的。这就是为什么任何这样的实现都将是一个(希望足够好)近似值。
【解决方案2】:

看起来已经有一个名为Classmexer 的实用程序可以执行此操作。

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

【讨论】:

    【解决方案3】:

    一个好的通用解决方案是使用堆大小增量。这涉及最少的工作,并且可以在任何类型的对象/对象图之间重用。通过多次实例化和销毁您的对象并在其间进行垃圾收集,然后取平均值,您可以避免编译器和 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
    

    【讨论】:

    • 这是有缺陷的,因为 gc() 和 runFinalization() 方法不是确定性的。即:它们只是运行时执行这些操作的提示。运行时忽略它们是完全合法的。
    • 它没有缺陷 - 正如你所说,它们有时会被忽略,这就是为什么要进行大量迭代和平均结果的原因。
    • 你至少可以记下这段代码的来源,即使你稍微调整了一下:javaworld.com/javaworld/javatips/jw-javatip130.html
    • 这对于“如何以编程方式确定给定 Java 对象使用多少内存”这一问题不是的一个很好的解决方案。
    猜你喜欢
    • 2018-03-20
    • 2012-09-15
    • 1970-01-01
    • 1970-01-01
    • 2013-03-31
    • 2014-03-20
    • 1970-01-01
    • 2015-05-22
    • 1970-01-01
    相关资源
    最近更新 更多