Подтвердить что ты не робот

Программно рассчитать память, занимаемую Java-объектом, включая объекты, которые она ссылается

Мне нужно программно узнать, сколько памяти занимает данный Java-объект, включая память, занимаемую объектами, на которые она ссылается.

Я могу создать дамп кучи памяти и проанализировать результаты с помощью инструмента. Однако для создания дампа кучи требуется значительное количество времени, и для такого инструмента можно прочитать дамп для создания отчетов. Учитывая тот факт, что мне, вероятно, понадобится сделать это несколько раз, моя работа была бы намного более подвижной, если бы я мог вставить какой-то код в свой проект, который дал бы мне это значение "runtime".

Как я мог бы достичь этого?

ps: В частности, у меня есть набор объектов типа javax.xml.transform.Templates

4b9b3361

Ответ 1

Для этого вам нужно использовать отражение. Получившийся фрагмент кода слишком сложный для меня, чтобы опубликовать его здесь (хотя он скоро будет доступен как часть инструментария GPL, который я создаю), но основная идея:

  • Заголовок объекта использует 8 байтов (для указателя класса и количества ссылок)
  • Каждое примитивное поле использует 1, 2, 4 или 8 байтов в зависимости от фактического типа
  • Каждое поле ссылки объекта (то есть непримитивное) использует 4 байта (ссылка, плюс все, на что ссылается объект)

Вам нужно обрабатывать массивы отдельно (8 байтов заголовка, 4 байта поля длины, 4 * байтов длины таблицы, плюс все, что используются внутри объектов). Вам нужно обработать другие типы объектов, итерации через поля (и поля родителей), используя отражение.

Вам также необходимо сохранить набор "видимых" объектов во время рекурсии, чтобы не посчитать несколько объектов, на которые ссылаются в нескольких местах.

Ответ 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