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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/757300/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-29 13:38:57  来源:igfitidea点击:

Programmatically calculate memory occupied by a Java Object including objects it references

javamemory-management

提问by Kishnan

I need to programmatically find out exactly how much memory a given Java Object is occupying including the memory occupied by the objects that it is referencing.

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

I can generate a memory heap dump and analyse the results using a tool. However it takes a considerable amount of time to generate a heap dump and for such a tool to read the dump to generate reports. Given the fact that I will probably need to do this several times, my work would be a lot more agile if I could throw in some code in my project that would give me this value "runtime".

我可以生成内存堆转储并使用工具分析结果。但是,生成堆转储以及此类工具读取转储以生成报告需要花费大量时间。鉴于我可能需要多次执行此操作这一事实,如果我可以在我的项目中添加一些代码来为我提供“运行时”这个值,那么我的工作将会更加敏捷。

How could I best achieve this?

我怎样才能最好地实现这一目标?

ps: Specifically I have a collection of objects of type javax.xml.transform.Templates

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

采纳答案by Varkhan

You will need to use reflectionfor that. The resulting piece of code is too complicated for me to post here (although it will soon be available as part of a GPL toolkit I am building), but the main idea is:

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

  • An object header uses 8 bytes (for class pointer and reference count)
  • Each primitive field uses 1, 2, 4 or 8 bytes depending on the actual type
  • Each object reference (i.e. non-primitive) field uses 4 bytes (the reference, plus whatever the referenced object uses)
  • 一个对象头使用 8 个字节(用于类指针和引用计数)
  • 每个原始字段根据实际类型使用 1、2、4 或 8 个字节
  • 每个对象引用(即非原始)字段使用 4 个字节(引用,加上被引用对象使用的任何内容)

You need to treat arrays separately (8 bytes of header, 4 bytes of length field, 4*length bytes of table, plus whatever the objects inside are using). You need to process other types of objects iterating through the fields (and it's parents fields) using reflection.

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

You also need to keep a set of "seen" objects during the recursion, so as not to count multiple times objects referenced in several places.

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

回答by Robin

Looks like there is already a utility for doing this called Classmexer.

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

I haven't tried it myself, but I would go that route before I rolled my own.

我自己还没有尝试过,但在我自己动手之前我会走那条路。

回答by Peter

A good generic solution is to use heap size delta. This involves minimal effort and is re-usable between any type of object / object graph. By instantiating and destroying your objects many times and garbage collecting in between, and then taking the average, you avoid compiler and JVM optimizations that alter results and get a fairly accurate result. If you need an EXACT answer down to the byte then this may not be the solution for you, but for all practical applications that I know of (profiling, memory requirement calcualtions) it works extremely well. The code below will do just that.

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