main.shark.internal.HprofInMemoryIndex.kt Maven / Gradle / Ivy
package shark.internal
import java.util.EnumSet
import kotlin.math.max
import shark.GcRoot
import shark.GcRoot.StickyClass
import shark.HprofHeader
import shark.HprofRecordReader
import shark.HprofRecordTag
import shark.HprofRecordTag.CLASS_DUMP
import shark.HprofRecordTag.INSTANCE_DUMP
import shark.HprofRecordTag.LOAD_CLASS
import shark.HprofRecordTag.OBJECT_ARRAY_DUMP
import shark.HprofRecordTag.PRIMITIVE_ARRAY_DUMP
import shark.HprofRecordTag.ROOT_DEBUGGER
import shark.HprofRecordTag.ROOT_FINALIZING
import shark.HprofRecordTag.ROOT_INTERNED_STRING
import shark.HprofRecordTag.ROOT_JAVA_FRAME
import shark.HprofRecordTag.ROOT_JNI_GLOBAL
import shark.HprofRecordTag.ROOT_JNI_LOCAL
import shark.HprofRecordTag.ROOT_JNI_MONITOR
import shark.HprofRecordTag.ROOT_MONITOR_USED
import shark.HprofRecordTag.ROOT_NATIVE_STACK
import shark.HprofRecordTag.ROOT_REFERENCE_CLEANUP
import shark.HprofRecordTag.ROOT_STICKY_CLASS
import shark.HprofRecordTag.ROOT_THREAD_BLOCK
import shark.HprofRecordTag.ROOT_THREAD_OBJECT
import shark.HprofRecordTag.ROOT_UNKNOWN
import shark.HprofRecordTag.ROOT_UNREACHABLE
import shark.HprofRecordTag.ROOT_VM_INTERNAL
import shark.HprofRecordTag.STRING_IN_UTF8
import shark.HprofVersion.ANDROID
import shark.OnHprofRecordTagListener
import shark.PrimitiveType
import shark.PrimitiveType.INT
import shark.ProguardMapping
import shark.StreamingHprofReader
import shark.ValueHolder
import shark.internal.IndexedObject.IndexedClass
import shark.internal.IndexedObject.IndexedInstance
import shark.internal.IndexedObject.IndexedObjectArray
import shark.internal.IndexedObject.IndexedPrimitiveArray
import shark.internal.hppc.IntObjectPair
import shark.internal.hppc.LongLongScatterMap
import shark.internal.hppc.LongObjectPair
import shark.internal.hppc.LongObjectScatterMap
import shark.internal.hppc.LongScatterSet
import shark.internal.hppc.to
/**
* This class is not thread safe, should be used from a single thread.
*/
internal class HprofInMemoryIndex private constructor(
private val positionSize: Int,
private val hprofStringCache: LongObjectScatterMap,
private val classNames: LongLongScatterMap,
private val classIndex: SortedBytesMap,
private val instanceIndex: SortedBytesMap,
private val objectArrayIndex: SortedBytesMap,
private val primitiveArrayIndex: SortedBytesMap,
private val gcRoots: List,
private val proguardMapping: ProguardMapping?,
private val bytesForClassSize: Int,
private val bytesForInstanceSize: Int,
private val bytesForObjectArraySize: Int,
private val bytesForPrimitiveArraySize: Int,
private val useForwardSlashClassPackageSeparator: Boolean,
val classFieldsReader: ClassFieldsReader,
private val classFieldsIndexSize: Int,
private val stickyClassGcRootIds: LongScatterSet,
) {
val classCount: Int
get() = classIndex.size
val instanceCount: Int
get() = instanceIndex.size
val objectArrayCount: Int
get() = objectArrayIndex.size
val primitiveArrayCount: Int
get() = primitiveArrayIndex.size
fun fieldName(
classId: Long,
id: Long
): String {
val fieldNameString = hprofStringById(id)
return proguardMapping?.let {
val classNameStringId = classNames[classId]
val classNameString = hprofStringById(classNameStringId)
proguardMapping.deobfuscateFieldName(classNameString, fieldNameString)
} ?: fieldNameString
}
fun className(classId: Long): String {
// String, primitive types
val classNameStringId = classNames[classId]
val classNameString = hprofStringById(classNameStringId)
return (proguardMapping?.deobfuscateClassName(classNameString) ?: classNameString).run {
if (useForwardSlashClassPackageSeparator) {
// JVM heap dumps use "/" for package separators (vs "." for Android heap dumps)
replace('/', '.')
} else this
}
}
/**
* Returns the first class that matches the provided name, prioritizing system classes (as held
* by sticky class gc roots).
*
* On Android, all currently loaded classes are sticky. The heap dump may also contain classes
* that were unloaded but not garbage collected yet, leading to classes being present twice
* in the heap dump. To work around that we prioritize classes that are held by a sticky class GC
* root.
*/
fun classId(className: String): Long? {
val internalClassName = if (useForwardSlashClassPackageSeparator) {
// JVM heap dumps use "/" for package separators (vs "." for Android heap dumps)
className.replace('.', '/')
} else className
// Note: this performs two linear scans over arrays
val hprofStringId = hprofStringCache.entrySequence()
.firstOrNull { it.second == internalClassName }
?.first ?: return null
val classNamesIterator = classNames.entrySequence().iterator()
var firstNonStickyMatchingClass: Long? = null
while(classNamesIterator.hasNext()) {
val (classId, classNameStringId) = classNamesIterator.next()
if (hprofStringId == classNameStringId) {
if (classId in stickyClassGcRootIds) {
return classId
} else {
firstNonStickyMatchingClass = classId
}
}
}
return firstNonStickyMatchingClass
}
fun indexedClassSequence(): Sequence> {
return classIndex.entrySequence()
.map {
val id = it.first
val array = it.second
id to array.readClass()
}
}
fun indexedInstanceSequence(): Sequence> {
return instanceIndex.entrySequence()
.map {
val id = it.first
val array = it.second
val instance = IndexedInstance(
position = array.readTruncatedLong(positionSize),
classId = array.readId(),
recordSize = array.readTruncatedLong(bytesForInstanceSize)
)
id to instance
}
}
fun indexedObjectArraySequence(): Sequence> {
return objectArrayIndex.entrySequence()
.map {
val id = it.first
val array = it.second
val objectArray = IndexedObjectArray(
position = array.readTruncatedLong(positionSize),
arrayClassId = array.readId(),
recordSize = array.readTruncatedLong(bytesForObjectArraySize)
)
id to objectArray
}
}
fun indexedPrimitiveArraySequence(): Sequence> {
return primitiveArrayIndex.entrySequence()
.map {
val id = it.first
val array = it.second
val primitiveArray = IndexedPrimitiveArray(
position = array.readTruncatedLong(positionSize),
primitiveType = PrimitiveType.values()[array.readByte()
.toInt()],
recordSize = array.readTruncatedLong(bytesForPrimitiveArraySize)
)
id to primitiveArray
}
}
fun indexedObjectSequence(): Sequence> {
return indexedClassSequence() +
indexedInstanceSequence() +
indexedObjectArraySequence() +
indexedPrimitiveArraySequence()
}
fun gcRoots(): List {
return gcRoots
}
fun objectAtIndex(index: Int): LongObjectPair {
require(index > 0)
if (index < classIndex.size) {
val objectId = classIndex.keyAt(index)
val array = classIndex.getAtIndex(index)
return objectId to array.readClass()
}
var shiftedIndex = index - classIndex.size
if (shiftedIndex < instanceIndex.size) {
val objectId = instanceIndex.keyAt(shiftedIndex)
val array = instanceIndex.getAtIndex(shiftedIndex)
return objectId to IndexedInstance(
position = array.readTruncatedLong(positionSize),
classId = array.readId(),
recordSize = array.readTruncatedLong(bytesForInstanceSize)
)
}
shiftedIndex -= instanceIndex.size
if (shiftedIndex < objectArrayIndex.size) {
val objectId = objectArrayIndex.keyAt(shiftedIndex)
val array = objectArrayIndex.getAtIndex(shiftedIndex)
return objectId to IndexedObjectArray(
position = array.readTruncatedLong(positionSize),
arrayClassId = array.readId(),
recordSize = array.readTruncatedLong(bytesForObjectArraySize)
)
}
shiftedIndex -= objectArrayIndex.size
require(index < primitiveArrayIndex.size)
val objectId = primitiveArrayIndex.keyAt(shiftedIndex)
val array = primitiveArrayIndex.getAtIndex(shiftedIndex)
return objectId to IndexedPrimitiveArray(
position = array.readTruncatedLong(positionSize),
primitiveType = PrimitiveType.values()[array.readByte()
.toInt()],
recordSize = array.readTruncatedLong(bytesForPrimitiveArraySize)
)
}
@Suppress("ReturnCount")
fun indexedObjectOrNull(objectId: Long): IntObjectPair? {
var index = classIndex.indexOf(objectId)
if (index >= 0) {
val array = classIndex.getAtIndex(index)
return index to array.readClass()
}
index = instanceIndex.indexOf(objectId)
if (index >= 0) {
val array = instanceIndex.getAtIndex(index)
return classIndex.size + index to IndexedInstance(
position = array.readTruncatedLong(positionSize),
classId = array.readId(),
recordSize = array.readTruncatedLong(bytesForInstanceSize)
)
}
index = objectArrayIndex.indexOf(objectId)
if (index >= 0) {
val array = objectArrayIndex.getAtIndex(index)
return classIndex.size + instanceIndex.size + index to IndexedObjectArray(
position = array.readTruncatedLong(positionSize),
arrayClassId = array.readId(),
recordSize = array.readTruncatedLong(bytesForObjectArraySize)
)
}
index = primitiveArrayIndex.indexOf(objectId)
if (index >= 0) {
val array = primitiveArrayIndex.getAtIndex(index)
return classIndex.size + instanceIndex.size + index + primitiveArrayIndex.size to IndexedPrimitiveArray(
position = array.readTruncatedLong(positionSize),
primitiveType = PrimitiveType.values()[array.readByte()
.toInt()],
recordSize = array.readTruncatedLong(bytesForPrimitiveArraySize)
)
}
return null
}
private fun ByteSubArray.readClass(): IndexedClass {
val position = readTruncatedLong(positionSize)
val superclassId = readId()
val instanceSize = readInt()
val recordSize = readTruncatedLong(bytesForClassSize)
val fieldsIndex = readTruncatedLong(classFieldsIndexSize).toInt()
return IndexedClass(
position = position,
superclassId = superclassId,
instanceSize = instanceSize,
recordSize = recordSize,
fieldsIndex = fieldsIndex
)
}
@Suppress("ReturnCount")
fun objectIdIsIndexed(objectId: Long): Boolean {
if (classIndex[objectId] != null) {
return true
}
if (instanceIndex[objectId] != null) {
return true
}
if (objectArrayIndex[objectId] != null) {
return true
}
if (primitiveArrayIndex[objectId] != null) {
return true
}
return false
}
private fun hprofStringById(id: Long): String {
return hprofStringCache[id] ?: throw IllegalArgumentException("Hprof string $id not in cache")
}
private class Builder(
longIdentifiers: Boolean,
maxPosition: Long,
classCount: Int,
instanceCount: Int,
objectArrayCount: Int,
primitiveArrayCount: Int,
val bytesForClassSize: Int,
val bytesForInstanceSize: Int,
val bytesForObjectArraySize: Int,
val bytesForPrimitiveArraySize: Int,
val classFieldsTotalBytes: Int,
val stickyClassGcRootIds: LongScatterSet,
) : OnHprofRecordTagListener {
private val identifierSize = if (longIdentifiers) 8 else 4
private val positionSize = byteSizeForUnsigned(maxPosition)
private val classFieldsIndexSize = byteSizeForUnsigned(classFieldsTotalBytes.toLong())
/**
* Map of string id to string
* This currently keeps all the hprof strings that we could care about: class names,
* static field names and instance fields names
*/
// TODO Replacing with a radix trie reversed into a sparse array of long to trie leaf could save
// memory. Can be stored as 3 arrays: array of keys, array of values which are indexes into
// a large array of string bytes. Each "entry" consists of a size, the index of the previous
// segment and then the segment content.
private val hprofStringCache = LongObjectScatterMap()
/**
* class id to string id
*/
private val classNames = LongLongScatterMap(expectedElements = classCount)
private val classFieldBytes = ByteArray(classFieldsTotalBytes)
private var classFieldsIndex = 0
private val classIndex = UnsortedByteEntries(
bytesPerValue = positionSize + identifierSize + 4 + bytesForClassSize + classFieldsIndexSize,
longIdentifiers = longIdentifiers,
initialCapacity = classCount
)
private val instanceIndex = UnsortedByteEntries(
bytesPerValue = positionSize + identifierSize + bytesForInstanceSize,
longIdentifiers = longIdentifiers,
initialCapacity = instanceCount
)
private val objectArrayIndex = UnsortedByteEntries(
bytesPerValue = positionSize + identifierSize + bytesForObjectArraySize,
longIdentifiers = longIdentifiers,
initialCapacity = objectArrayCount
)
private val primitiveArrayIndex = UnsortedByteEntries(
bytesPerValue = positionSize + 1 + bytesForPrimitiveArraySize,
longIdentifiers = longIdentifiers,
initialCapacity = primitiveArrayCount
)
// Pre seeding gc roots with the sticky class gc roots we've already parsed.
private val gcRoots: MutableList = ArrayList(stickyClassGcRootIds.size()).apply {
stickyClassGcRootIds.elementSequence().forEach {classId ->
add(StickyClass(classId))
}
}
private fun HprofRecordReader.copyToClassFields(byteCount: Int) {
for (i in 1..byteCount) {
classFieldBytes[classFieldsIndex++] = readByte()
}
}
private fun lastClassFieldsShort() =
((classFieldBytes[classFieldsIndex - 2].toInt() and 0xff shl 8) or
(classFieldBytes[classFieldsIndex - 1].toInt() and 0xff)).toShort()
override fun onHprofRecord(
tag: HprofRecordTag,
length: Long,
reader: HprofRecordReader
) {
when (tag) {
STRING_IN_UTF8 -> {
hprofStringCache[reader.readId()] = reader.readUtf8(length - identifierSize)
}
LOAD_CLASS -> {
// classSerialNumber
reader.skip(INT.byteSize)
val id = reader.readId()
// stackTraceSerialNumber
reader.skip(INT.byteSize)
val classNameStringId = reader.readId()
classNames[id] = classNameStringId
}
ROOT_UNKNOWN -> {
reader.readUnknownGcRootRecord().apply {
if (id != ValueHolder.NULL_REFERENCE) {
gcRoots += this
}
}
}
ROOT_JNI_GLOBAL -> {
reader.readJniGlobalGcRootRecord().apply {
if (id != ValueHolder.NULL_REFERENCE) {
gcRoots += this
}
}
}
ROOT_JNI_LOCAL -> {
reader.readJniLocalGcRootRecord().apply {
if (id != ValueHolder.NULL_REFERENCE) {
gcRoots += this
}
}
}
ROOT_JAVA_FRAME -> {
reader.readJavaFrameGcRootRecord().apply {
if (id != ValueHolder.NULL_REFERENCE) {
gcRoots += this
}
}
}
ROOT_NATIVE_STACK -> {
reader.readNativeStackGcRootRecord().apply {
if (id != ValueHolder.NULL_REFERENCE) {
gcRoots += this
}
}
}
ROOT_STICKY_CLASS -> {
// We already parse these gc roots in the initial scan.
reader.skipId()
}
ROOT_THREAD_BLOCK -> {
reader.readThreadBlockGcRootRecord().apply {
if (id != ValueHolder.NULL_REFERENCE) {
gcRoots += this
}
}
}
ROOT_MONITOR_USED -> {
reader.readMonitorUsedGcRootRecord().apply {
if (id != ValueHolder.NULL_REFERENCE) {
gcRoots += this
}
}
}
ROOT_THREAD_OBJECT -> {
reader.readThreadObjectGcRootRecord().apply {
if (id != ValueHolder.NULL_REFERENCE) {
gcRoots += this
}
}
}
ROOT_INTERNED_STRING -> {
reader.readInternedStringGcRootRecord().apply {
if (id != ValueHolder.NULL_REFERENCE) {
gcRoots += this
}
}
}
ROOT_FINALIZING -> {
reader.readFinalizingGcRootRecord().apply {
if (id != ValueHolder.NULL_REFERENCE) {
gcRoots += this
}
}
}
ROOT_DEBUGGER -> {
reader.readDebuggerGcRootRecord().apply {
if (id != ValueHolder.NULL_REFERENCE) {
gcRoots += this
}
}
}
ROOT_REFERENCE_CLEANUP -> {
reader.readReferenceCleanupGcRootRecord().apply {
if (id != ValueHolder.NULL_REFERENCE) {
gcRoots += this
}
}
}
ROOT_VM_INTERNAL -> {
reader.readVmInternalGcRootRecord().apply {
if (id != ValueHolder.NULL_REFERENCE) {
gcRoots += this
}
}
}
ROOT_JNI_MONITOR -> {
reader.readJniMonitorGcRootRecord().apply {
if (id != ValueHolder.NULL_REFERENCE) {
gcRoots += this
}
}
}
ROOT_UNREACHABLE -> {
reader.readUnreachableGcRootRecord().apply {
if (id != ValueHolder.NULL_REFERENCE) {
gcRoots += this
}
}
}
CLASS_DUMP -> {
val bytesReadStart = reader.bytesRead
val id = reader.readId()
// stack trace serial number
reader.skip(INT.byteSize)
val superclassId = reader.readId()
reader.skip(5 * identifierSize)
// instance size (in bytes)
// Useful to compute retained size
val instanceSize = reader.readInt()
reader.skipClassDumpConstantPool()
val startPosition = classFieldsIndex
val bytesReadFieldStart = reader.bytesRead
reader.copyToClassFields(2)
val staticFieldCount = lastClassFieldsShort().toInt() and 0xFFFF
for (i in 0 until staticFieldCount) {
reader.copyToClassFields(identifierSize)
reader.copyToClassFields(1)
val type = classFieldBytes[classFieldsIndex - 1].toInt() and 0xff
if (type == PrimitiveType.REFERENCE_HPROF_TYPE) {
reader.copyToClassFields(identifierSize)
} else {
reader.copyToClassFields(PrimitiveType.byteSizeByHprofType.getValue(type))
}
}
reader.copyToClassFields(2)
val fieldCount = lastClassFieldsShort().toInt() and 0xFFFF
for (i in 0 until fieldCount) {
reader.copyToClassFields(identifierSize)
reader.copyToClassFields(1)
}
val fieldsSize = (reader.bytesRead - bytesReadFieldStart).toInt()
val recordSize = reader.bytesRead - bytesReadStart
classIndex.append(id)
.apply {
writeTruncatedLong(bytesReadStart, positionSize)
writeId(superclassId)
writeInt(instanceSize)
writeTruncatedLong(recordSize, bytesForClassSize)
writeTruncatedLong(startPosition.toLong(), classFieldsIndexSize)
}
require(startPosition + fieldsSize == classFieldsIndex) {
"Expected $classFieldsIndex to have moved by $fieldsSize and be equal to ${startPosition + fieldsSize}"
}
}
INSTANCE_DUMP -> {
val bytesReadStart = reader.bytesRead
val id = reader.readId()
reader.skip(INT.byteSize)
val classId = reader.readId()
val remainingBytesInInstance = reader.readInt()
reader.skip(remainingBytesInInstance)
val recordSize = reader.bytesRead - bytesReadStart
instanceIndex.append(id)
.apply {
writeTruncatedLong(bytesReadStart, positionSize)
writeId(classId)
writeTruncatedLong(recordSize, bytesForInstanceSize)
}
}
OBJECT_ARRAY_DUMP -> {
val bytesReadStart = reader.bytesRead
val id = reader.readId()
// stack trace serial number
reader.skip(INT.byteSize)
val size = reader.readInt()
val arrayClassId = reader.readId()
reader.skip(identifierSize * size)
// record size - (ID+INT + INT + ID)
val recordSize = reader.bytesRead - bytesReadStart
objectArrayIndex.append(id)
.apply {
writeTruncatedLong(bytesReadStart, positionSize)
writeId(arrayClassId)
writeTruncatedLong(recordSize, bytesForObjectArraySize)
}
}
PRIMITIVE_ARRAY_DUMP -> {
val bytesReadStart = reader.bytesRead
val id = reader.readId()
reader.skip(INT.byteSize)
val size = reader.readInt()
val type = PrimitiveType.primitiveTypeByHprofType.getValue(reader.readUnsignedByte())
reader.skip(size * type.byteSize)
val recordSize = reader.bytesRead - bytesReadStart
primitiveArrayIndex.append(id)
.apply {
writeTruncatedLong(bytesReadStart, positionSize)
writeByte(type.ordinal.toByte())
writeTruncatedLong(recordSize, bytesForPrimitiveArraySize)
}
}
else -> {
// Not interesting.
}
}
}
fun buildIndex(
proguardMapping: ProguardMapping?,
hprofHeader: HprofHeader
): HprofInMemoryIndex {
require(classFieldsIndex == classFieldBytes.size) {
"Read $classFieldsIndex into fields bytes instead of expected ${classFieldBytes.size}"
}
val sortedInstanceIndex = instanceIndex.moveToSortedMap()
val sortedObjectArrayIndex = objectArrayIndex.moveToSortedMap()
val sortedPrimitiveArrayIndex = primitiveArrayIndex.moveToSortedMap()
val sortedClassIndex = classIndex.moveToSortedMap()
// Passing references to avoid copying the underlying data structures.
return HprofInMemoryIndex(
positionSize = positionSize,
hprofStringCache = hprofStringCache,
classNames = classNames,
classIndex = sortedClassIndex,
instanceIndex = sortedInstanceIndex,
objectArrayIndex = sortedObjectArrayIndex,
primitiveArrayIndex = sortedPrimitiveArrayIndex,
gcRoots = gcRoots,
proguardMapping = proguardMapping,
bytesForClassSize = bytesForClassSize,
bytesForInstanceSize = bytesForInstanceSize,
bytesForObjectArraySize = bytesForObjectArraySize,
bytesForPrimitiveArraySize = bytesForPrimitiveArraySize,
useForwardSlashClassPackageSeparator = hprofHeader.version != ANDROID,
classFieldsReader = ClassFieldsReader(identifierSize, classFieldBytes),
classFieldsIndexSize = classFieldsIndexSize,
stickyClassGcRootIds = stickyClassGcRootIds,
)
}
}
companion object {
private fun byteSizeForUnsigned(maxValue: Long): Int {
var value = maxValue
var byteCount = 0
while (value != 0L) {
value = value shr 8
byteCount++
}
return byteCount
}
fun indexHprof(
reader: StreamingHprofReader,
hprofHeader: HprofHeader,
proguardMapping: ProguardMapping?,
indexedGcRootTags: Set
): HprofInMemoryIndex {
// First pass to count and correctly size arrays once and for all.
var maxClassSize = 0L
var maxInstanceSize = 0L
var maxObjectArraySize = 0L
var maxPrimitiveArraySize = 0L
var classCount = 0
var instanceCount = 0
var objectArrayCount = 0
var primitiveArrayCount = 0
var classFieldsTotalBytes = 0
val stickyClassGcRootIds = LongScatterSet()
val bytesRead = reader.readRecords(
EnumSet.of(
CLASS_DUMP,
INSTANCE_DUMP,
OBJECT_ARRAY_DUMP,
PRIMITIVE_ARRAY_DUMP,
ROOT_STICKY_CLASS
)
) { tag, _, reader ->
val bytesReadStart = reader.bytesRead
when (tag) {
CLASS_DUMP -> {
classCount++
reader.skipClassDumpHeader()
val bytesReadStaticFieldStart = reader.bytesRead
reader.skipClassDumpStaticFields()
reader.skipClassDumpFields()
maxClassSize = max(maxClassSize, reader.bytesRead - bytesReadStart)
classFieldsTotalBytes += (reader.bytesRead - bytesReadStaticFieldStart).toInt()
}
INSTANCE_DUMP -> {
instanceCount++
reader.skipInstanceDumpRecord()
maxInstanceSize = max(maxInstanceSize, reader.bytesRead - bytesReadStart)
}
OBJECT_ARRAY_DUMP -> {
objectArrayCount++
reader.skipObjectArrayDumpRecord()
maxObjectArraySize = max(maxObjectArraySize, reader.bytesRead - bytesReadStart)
}
PRIMITIVE_ARRAY_DUMP -> {
primitiveArrayCount++
reader.skipPrimitiveArrayDumpRecord()
maxPrimitiveArraySize = max(maxPrimitiveArraySize, reader.bytesRead - bytesReadStart)
}
ROOT_STICKY_CLASS -> {
// StickyClass has only 1 field: id. Our API 23 emulators in CI are creating heap
// dumps with duplicated sticky class roots, up to 30K times for some objects.
// There's no point in keeping all these in our list of roots, 1 per each is enough
// so we deduplicate with stickyClassGcRootIds.
val id = reader.readStickyClassGcRootRecord().id
if (id != ValueHolder.NULL_REFERENCE) {
stickyClassGcRootIds += id
}
}
else -> {
// Not interesting.
}
}
}
val bytesForClassSize = byteSizeForUnsigned(maxClassSize)
val bytesForInstanceSize = byteSizeForUnsigned(maxInstanceSize)
val bytesForObjectArraySize = byteSizeForUnsigned(maxObjectArraySize)
val bytesForPrimitiveArraySize = byteSizeForUnsigned(maxPrimitiveArraySize)
val indexBuilderListener = Builder(
longIdentifiers = hprofHeader.identifierByteSize == 8,
maxPosition = bytesRead,
classCount = classCount,
instanceCount = instanceCount,
objectArrayCount = objectArrayCount,
primitiveArrayCount = primitiveArrayCount,
bytesForClassSize = bytesForClassSize,
bytesForInstanceSize = bytesForInstanceSize,
bytesForObjectArraySize = bytesForObjectArraySize,
bytesForPrimitiveArraySize = bytesForPrimitiveArraySize,
classFieldsTotalBytes = classFieldsTotalBytes,
stickyClassGcRootIds
)
val recordTypes = EnumSet.of(
STRING_IN_UTF8,
LOAD_CLASS,
CLASS_DUMP,
INSTANCE_DUMP,
OBJECT_ARRAY_DUMP,
PRIMITIVE_ARRAY_DUMP
) + HprofRecordTag.rootTags.intersect(indexedGcRootTags)
reader.readRecords(recordTypes, indexBuilderListener)
return indexBuilderListener.buildIndex(proguardMapping, hprofHeader)
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy