Расчет оставшегося размера, включая переменные фрейма стека?
Я нашел тонну вопросов о "сохраненном размере", и принятый ответ кажется:
Оставшийся размер для объекта - это количество памяти, которое этот объект сохраняет от сборки мусора.
Теперь я работаю над программным вычислением оставшегося размера в 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
сущностей, но это достаточно близко для меня.