Расчет оставшегося размера, включая переменные фрейма стека?

Я нашел тонну вопросов о "сохраненном размере", и принятый ответ кажется:

Оставшийся размер для объекта - это количество памяти, которое этот объект сохраняет от сборки мусора.

Теперь я работаю над программным вычислением оставшегося размера в hprof файл (как определено здесь) с использованием библиотеки профилировщика Netbeans (расчет оставшегося размера выполняется в HprofHeap.java). Работает просто отлично (извините, для краткости использовал kotlin):

val heap: Heap = HeapFactory.createHeap(myHeap.toFile())
val threadClass: JavaClass = heap.getJavaClassByName("java.lang.Thread")
val instanceFilter = { it: Instance -> threadClass == it.getJavaClass() }
val sizeMap = heap.allInstances
            .filter { instanceFilter(it) }
            .toMap({ findThreadName(it) /* not shown */ }, { it.retainedSize })

Что я заметил, когда sizeMap было только предельное число сохраненных размеров, это то, что Netbeans вычисляет сохраненные размеры только для объектов, которые не находятся в стеке. Таким образом, локальные переменные (размещенные в стеке) назначаются Thread не будет включен в оставшийся размер.

Мой вопрос: есть ли способ заставить библиотеку netbeans рассматривать элементы стека как зависимые объекты, как, например, Yourkit Profiler выполняет свои вычисления? Как мне добавить такую ​​функцию, если ответ на предыдущий вопрос "нет"?

1 ответ

Решение

Немного копания обнаружил, что дампер JVM создает запись типа ROOT JAVA FRAME для локальной переменной стека (сравните VM_HeapDumper:: do_thread). Так как я могу найти это в куче, вот что я сделал:

val threadClass: JavaClass = heap.getJavaClassByName("java.lang.Thread")

val keyTransformer = { it: Instance -> findThreadName(it) }
val instanceFilter = { it: Instance -> it.getJavaClass() == threadClass }

val stackLocals = heap.gcRoots
        .filter { it.kind == GCRoot.JAVA_FRAME }
        .groupBy { (it as JavaFrameGCRoot).threadGCRoot }

val sizeMap = heap.allInstances
      .filter { instanceFilter(it) }
      .toMap(
          { keyTransformer(it) }, 
          { 
              val locals = stackLocals[heap.getGCRoot(it)]
              val localSize = locals!!.sumBy { it.instance.retainedSize.toInt() }
              it.retainedSize + localSize
          })

return Report(
        sizeMap.values.sum(),
        sizeMap.keys.size.toLong(),
        sizeMap.maxBy { it.value }?.let { it.toPair() } ?: ("n/a" to 0L))

Это решение основано на поиске корня GC для каждого потока (должен быть Thread сам), затем сортируйте в сохраненный корень gc JAVA FRAME (идентификатор потока [= GC root] является частью сохраненных входных данных).

Все еще есть небольшая разница по сравнению со значениями из Yourkit, вероятно, из-за того, что я пропал ROOT JNI LOCAL сущностей, но это достаточно близко для меня.

Другие вопросы по тегам