HprofRecordReader.kt
TLDR
The HprofRecordReader.kt
file is responsible for reading hprof content from an Okio BufferedSource
. It is used to parse hprof dump files and extract various types of records such as string records, class records, stack trace records, and more.
Methods
readStringRecord
This method reads a string record from the hprof file.
readLoadClassRecord
This method reads a load class record from the hprof file.
readStackFrameRecord
This method reads a stack frame record from the hprof file.
readStackTraceRecord
This method reads a stack trace record from the hprof file.
readUnknownGcRootRecord
This method reads an unknown garbage collection (GC) root record from the hprof file.
readJniGlobalGcRootRecord
This method reads a JNI global GC root record from the hprof file.
readJniLocalGcRootRecord
This method reads a JNI local GC root record from the hprof file.
readJavaFrameGcRootRecord
This method reads a Java frame GC root record from the hprof file.
readNativeStackGcRootRecord
This method reads a native stack GC root record from the hprof file.
readStickyClassGcRootRecord
This method reads a sticky class GC root record from the hprof file.
readThreadBlockGcRootRecord
This method reads a thread block GC root record from the hprof file.
readMonitorUsedGcRootRecord
This method reads a monitor used GC root record from the hprof file.
readThreadObjectGcRootRecord
This method reads a thread object GC root record from the hprof file.
readInternedStringGcRootRecord
This method reads an interned string GC root record from the hprof file.
readFinalizingGcRootRecord
This method reads a finalizing GC root record from the hprof file.
readDebuggerGcRootRecord
This method reads a debugger GC root record from the hprof file.
readReferenceCleanupGcRootRecord
This method reads a reference cleanup GC root record from the hprof file.
readVmInternalGcRootRecord
This method reads a VM internal GC root record from the hprof file.
readJniMonitorGcRootRecord
This method reads a JNI monitor GC root record from the hprof file.
readUnreachableGcRootRecord
This method reads an unreachable GC root record from the hprof file.
readInstanceDumpRecord
This method reads a full instance record from the hprof file.
readHeapDumpInfoRecord
This method reads a heap dump info record from the hprof file.
readClassDumpRecord
This method reads a full class record from the hprof file.
readPrimitiveArrayDumpRecord
This method reads a full primitive array record from the hprof file.
readObjectArrayDumpRecord
This method reads a full object array record from the hprof file.
skipClassDumpHeader
This method skips the header of a class dump record in the hprof file.
skipClassDumpConstantPool
This method skips the constant pool of a class dump record in the hprof file.
skipClassDumpStaticFields
This method skips the static fields of a class dump record in the hprof file.
skipClassDumpFields
This method skips the fields of a class dump record in the hprof file.
skipInstanceDumpRecord
This method skips an instance record in the hprof file.
skipClassDumpRecord
This method skips a class dump record in the hprof file.
skipObjectArrayDumpRecord
This method skips an object array record in the hprof file.
skipPrimitiveArrayDumpRecord
This method skips a primitive array record in the hprof file.
skipHeapDumpInfoRecord
This method skips a heap dump info record in the hprof file.
skip
This method skips a specified number of bytes in the hprof file.
skipId
This method skips an ID (identifier) in the hprof file.
readUnsignedInt
This method reads an unsigned int from the hprof file.
readUnsignedByte
This method reads an unsigned byte from the hprof file.
readValue
This method reads a value in the heap dump, which can be a reference or a primitive type.
readShort
This method reads a short from the hprof file.
readInt
This method reads an int from the hprof file.
readIdArray
This method reads an array of IDs from the hprof file.
readBooleanArray
This method reads a boolean array from the hprof file.
readCharArray
This method reads a char array from the hprof file.
readString
This method reads a string from the hprof file.
readFloatArray
This method reads a float array from the hprof file.
readDoubleArray
This method reads a double array from the hprof file.
readShortArray
This method reads a short array from the hprof file.
readIntArray
This method reads an int array from the hprof file.
readLongArray
This method reads a long array from the hprof file.
readLong
This method reads a long from the hprof file.
readByte
This method reads a byte from the hprof file.
readBoolean
This method reads a boolean from the hprof file.
readByteArray
This method reads a byte array from the hprof file.
readChar
This method reads a char from the hprof file.
readFloat
This method reads a float from the hprof file.
readDouble
This method reads a double from the hprof file.
readId
This method reads an ID (identifier) from the hprof file.
readUtf8
This method reads a UTF-8 string from the hprof file.
readUnsignedShort
This method reads an unsigned short from the hprof file.
package shark
import okio.BufferedSource
import shark.GcRoot.Debugger
import shark.GcRoot.Finalizing
import shark.GcRoot.InternedString
import shark.GcRoot.JavaFrame
import shark.GcRoot.JniGlobal
import shark.GcRoot.JniLocal
import shark.GcRoot.JniMonitor
import shark.GcRoot.MonitorUsed
import shark.GcRoot.NativeStack
import shark.GcRoot.ReferenceCleanup
import shark.GcRoot.StickyClass
import shark.GcRoot.ThreadBlock
import shark.GcRoot.ThreadObject
import shark.GcRoot.Unknown
import shark.GcRoot.Unreachable
import shark.GcRoot.VmInternal
import shark.HprofRecord.HeapDumpRecord.HeapDumpInfoRecord
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ClassDumpRecord
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ClassDumpRecord.FieldRecord
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ClassDumpRecord.StaticFieldRecord
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.InstanceDumpRecord
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ObjectArrayDumpRecord
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.BooleanArrayDump
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.ByteArrayDump
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.CharArrayDump
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.DoubleArrayDump
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.FloatArrayDump
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.IntArrayDump
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.LongArrayDump
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.PrimitiveArrayDumpRecord.ShortArrayDump
import shark.HprofRecord.LoadClassRecord
import shark.HprofRecord.StackFrameRecord
import shark.HprofRecord.StackTraceRecord
import shark.HprofRecord.StringRecord
import shark.PrimitiveType.BOOLEAN
import shark.PrimitiveType.BYTE
import shark.PrimitiveType.CHAR
import shark.PrimitiveType.DOUBLE
import shark.PrimitiveType.FLOAT
import shark.PrimitiveType.INT
import shark.PrimitiveType.LONG
import shark.PrimitiveType.SHORT
import shark.ValueHolder.BooleanHolder
import shark.ValueHolder.ByteHolder
import shark.ValueHolder.CharHolder
import shark.ValueHolder.DoubleHolder
import shark.ValueHolder.FloatHolder
import shark.ValueHolder.IntHolder
import shark.ValueHolder.LongHolder
import shark.ValueHolder.ReferenceHolder
import shark.ValueHolder.ShortHolder
import java.nio.charset.Charset
/**
* Reads hprof content from an Okio [BufferedSource].
*
* Binary Dump Format reference: http://hg.openjdk.java.net/jdk6/jdk6/jdk/raw-file/tip/src/share
* /demo/jvmti/hprof/manual.html#mozTocId848088
*
* The Android Hprof format differs in some ways from that reference. This parser implementation
* is largely adapted from https://android.googlesource.com/platform/tools/base/+/studio-master-dev
* /perflib/src/main/java/com/android/tools/perflib
*
* Not thread safe, should be used from a single thread.
*/
@Suppress("LargeClass", "TooManyFunctions")
class HprofRecordReader internal constructor(
header: HprofHeader,
private val source: BufferedSource
) {
/**
* How many bytes this reader has read from [source]. Can only increase.
*/
var bytesRead = 0L
private set
private val identifierByteSize = header.identifierByteSize
private val typeSizes: IntArray
init {
val typeSizesMap =
PrimitiveType.byteSizeByHprofType + (PrimitiveType.REFERENCE_HPROF_TYPE to identifierByteSize)
val maxKey = typeSizesMap.keys.max()!!
typeSizes = IntArray(maxKey + 1) { key ->
typeSizesMap[key] ?: 0
}
}
fun sizeOf(type: Int) = typeSizes[type]
fun readStringRecord(length: Long) = StringRecord(
id = readId(),
string = readUtf8(length - identifierByteSize)
)
fun readLoadClassRecord() = LoadClassRecord(
classSerialNumber = readInt(),
id = readId(),
stackTraceSerialNumber = readInt(),
classNameStringId = readId()
)
fun readStackFrameRecord() = StackFrameRecord(
id = readId(),
methodNameStringId = readId(),
methodSignatureStringId = readId(),
sourceFileNameStringId = readId(),
classSerialNumber = readInt(),
lineNumber = readInt()
)
fun readStackTraceRecord() = StackTraceRecord(
stackTraceSerialNumber = readInt(),
threadSerialNumber = readInt(),
stackFrameIds = readIdArray(readInt())
)
fun readUnknownGcRootRecord() = Unknown(id = readId())
fun readJniGlobalGcRootRecord() = JniGlobal(
id = readId(),
jniGlobalRefId = readId()
)
fun readJniLocalGcRootRecord() = JniLocal(
id = readId(),
threadSerialNumber = readInt(),
frameNumber = readInt()
)
fun readJavaFrameGcRootRecord() = JavaFrame(
id = readId(),
threadSerialNumber = readInt(),
frameNumber = readInt()
)
fun readNativeStackGcRootRecord() = NativeStack(
id = readId(),
threadSerialNumber = readInt()
)
fun readStickyClassGcRootRecord() = StickyClass(id = readId())
fun readThreadBlockGcRootRecord() = ThreadBlock(id = readId(), threadSerialNumber = readInt())
fun readMonitorUsedGcRootRecord() = MonitorUsed(id = readId())
fun readThreadObjectGcRootRecord() = ThreadObject(
id = readId(),
threadSerialNumber = readInt(),
stackTraceSerialNumber = readInt()
)
fun readInternedStringGcRootRecord() = InternedString(id = readId())
fun readFinalizingGcRootRecord() = Finalizing(id = readId())
fun readDebuggerGcRootRecord() = Debugger(id = readId())
fun readReferenceCleanupGcRootRecord() = ReferenceCleanup(id = readId())
fun readVmInternalGcRootRecord() = VmInternal(id = readId())
fun readJniMonitorGcRootRecord() = JniMonitor(
id = readId(),
stackTraceSerialNumber = readInt(),
stackDepth = readInt()
)
fun readUnreachableGcRootRecord() = Unreachable(id = readId())
/**
* Reads a full instance record after a instance dump tag.
*/
fun readInstanceDumpRecord(): InstanceDumpRecord {
val id = readId()
val stackTraceSerialNumber = readInt()
val classId = readId()
val remainingBytesInInstance = readInt()
val fieldValues = readByteArray(remainingBytesInInstance)
return InstanceDumpRecord(
id = id,
stackTraceSerialNumber = stackTraceSerialNumber,
classId = classId,
fieldValues = fieldValues
)
}
fun readHeapDumpInfoRecord(): HeapDumpInfoRecord {
val heapId = readInt()
return HeapDumpInfoRecord(heapId = heapId, heapNameStringId = readId())
}
/**
* Reads a full class record after a class dump tag.
*/
fun readClassDumpRecord(): ClassDumpRecord {
val id = readId()
// stack trace serial number
val stackTraceSerialNumber = readInt()
val superclassId = readId()
// class loader object ID
val classLoaderId = readId()
// signers object ID
val signersId = readId()
// protection domain object ID
val protectionDomainId = readId()
// reserved
readId()
// reserved
readId()
// instance size (in bytes)
// Useful to compute retained size
val instanceSize = readInt()
// Skip over the constant pool
val constantPoolCount = readUnsignedShort()
for (i in 0 until constantPoolCount) {
// constant pool index
skip(SHORT_SIZE)
skip(typeSizes[readUnsignedByte()])
}
val staticFieldCount = readUnsignedShort()
val staticFields = ArrayList<StaticFieldRecord>(staticFieldCount)
for (i in 0 until staticFieldCount) {
val nameStringId = readId()
val type = readUnsignedByte()
val value = readValue(type)
staticFields.add(
StaticFieldRecord(
nameStringId = nameStringId,
type = type,
value = value
)
)
}
val fieldCount = readUnsignedShort()
val fields = ArrayList<FieldRecord>(fieldCount)
for (i in 0 until fieldCount) {
fields.add(FieldRecord(nameStringId = readId(), type = readUnsignedByte()))
}
return ClassDumpRecord(
id = id,
stackTraceSerialNumber = stackTraceSerialNumber,
superclassId = superclassId,
classLoaderId = classLoaderId,
signersId = signersId,
protectionDomainId = protectionDomainId,
instanceSize = instanceSize,
staticFields = staticFields,
fields = fields
)
}
/**
* Reads a full primitive array record after a primitive array dump tag.
*/
fun readPrimitiveArrayDumpRecord(): PrimitiveArrayDumpRecord {
val id = readId()
val stackTraceSerialNumber = readInt()
// length
val arrayLength = readInt()
return when (val type = readUnsignedByte()) {
BOOLEAN_TYPE -> BooleanArrayDump(
id, stackTraceSerialNumber, readBooleanArray(arrayLength)
)
CHAR_TYPE -> CharArrayDump(
id, stackTraceSerialNumber, readCharArray(arrayLength)
)
FLOAT_TYPE -> FloatArrayDump(
id, stackTraceSerialNumber, readFloatArray(arrayLength)
)
DOUBLE_TYPE -> DoubleArrayDump(
id, stackTraceSerialNumber, readDoubleArray(arrayLength)
)
BYTE_TYPE -> ByteArrayDump(
id, stackTraceSerialNumber, readByteArray(arrayLength)
)
SHORT_TYPE -> ShortArrayDump(
id, stackTraceSerialNumber, readShortArray(arrayLength)
)
INT_TYPE -> IntArrayDump(
id, stackTraceSerialNumber, readIntArray(arrayLength)
)
LONG_TYPE -> LongArrayDump(
id, stackTraceSerialNumber, readLongArray(arrayLength)
)
else -> throw IllegalStateException("Unexpected type $type")
}
}
/**
* Reads a full object array record after a object array dump tag.
*/
fun readObjectArrayDumpRecord(): ObjectArrayDumpRecord {
val id = readId()
// stack trace serial number
val stackTraceSerialNumber = readInt()
val arrayLength = readInt()
val arrayClassId = readId()
val elementIds = readIdArray(arrayLength)
return ObjectArrayDumpRecord(
id = id,
stackTraceSerialNumber = stackTraceSerialNumber,
arrayClassId = arrayClassId,
elementIds = elementIds
)
}
fun skipClassDumpHeader() {
skip(INT_SIZE * 2 + identifierByteSize * 7)
skipClassDumpConstantPool()
}
fun skipClassDumpConstantPool() {
// Skip over the constant pool
val constantPoolCount = readUnsignedShort()
for (i in 0 until constantPoolCount) {
// constant pool index
skip(SHORT.byteSize)
skip(sizeOf(readUnsignedByte()))
}
}
fun skipClassDumpStaticFields() {
val staticFieldCount = readUnsignedShort()
for (i in 0 until staticFieldCount) {
skip(identifierByteSize)
val type = readUnsignedByte()
skip(
if (type == PrimitiveType.REFERENCE_HPROF_TYPE) {
identifierByteSize
} else {
PrimitiveType.byteSizeByHprofType.getValue(type)
}
)
}
}
fun skipClassDumpFields() {
val fieldCount = readUnsignedShort()
skip((identifierByteSize + 1) * fieldCount)
}
fun skipInstanceDumpRecord() {
skip(identifierByteSize + INT_SIZE + identifierByteSize)
val remainingBytesInInstance = readInt()
skip(remainingBytesInInstance)
}
fun skipClassDumpRecord() {
skip(
identifierByteSize + INT_SIZE + identifierByteSize + identifierByteSize + identifierByteSize +
identifierByteSize + identifierByteSize + identifierByteSize + INT_SIZE
)
// Skip over the constant pool
val constantPoolCount = readUnsignedShort()
for (i in 0 until constantPoolCount) {
// constant pool index
skip(SHORT_SIZE)
skip(typeSizes[readUnsignedByte()])
}
val staticFieldCount = readUnsignedShort()
for (i in 0 until staticFieldCount) {
skip(identifierByteSize)
val type = readUnsignedByte()
skip(typeSizes[type])
}
val fieldCount = readUnsignedShort()
skip(fieldCount * (identifierByteSize + BYTE_SIZE))
}
fun skipObjectArrayDumpRecord() {
skip(identifierByteSize + INT_SIZE)
val arrayLength = readInt()
skip(identifierByteSize + arrayLength * identifierByteSize)
}
fun skipPrimitiveArrayDumpRecord() {
skip(identifierByteSize + INT_SIZE)
val arrayLength = readInt()
val type = readUnsignedByte()
skip(arrayLength * typeSizes[type])
}
fun skipHeapDumpInfoRecord() {
skip(identifierByteSize + identifierByteSize)
}
fun skip(byteCount: Int) {
bytesRead += byteCount
return source.skip(byteCount.toLong())
}
fun skipId() {
skip(identifierByteSize)
}
fun skip(byteCount: Long) {
bytesRead += byteCount
return source.skip(byteCount)
}
fun readUnsignedInt(): Long {
return readInt().toLong() and INT_MASK
}
fun readUnsignedByte(): Int {
return readByte().toInt() and BYTE_MASK
}
/**
* Reads a value in the heap dump, which can be a reference or a primitive type.
*/
fun readValue(type: Int): ValueHolder {
return when (type) {
PrimitiveType.REFERENCE_HPROF_TYPE -> ReferenceHolder(readId())
BOOLEAN_TYPE -> BooleanHolder(readBoolean())
CHAR_TYPE -> CharHolder(readChar())
FLOAT_TYPE -> FloatHolder(readFloat())
DOUBLE_TYPE -> DoubleHolder(readDouble())
BYTE_TYPE -> ByteHolder(readByte())
SHORT_TYPE -> ShortHolder(readShort())
INT_TYPE -> IntHolder(readInt())
LONG_TYPE -> LongHolder(readLong())
else -> throw IllegalStateException("Unknown type $type")
}
}
fun readShort(): Short {
bytesRead += SHORT_SIZE
return source.readShort()
}
fun readInt(): Int {
bytesRead += INT_SIZE
return source.readInt()
}
fun readIdArray(arrayLength: Int): LongArray {
return LongArray(arrayLength) { readId() }
}
fun readBooleanArray(arrayLength: Int): BooleanArray {
return BooleanArray(arrayLength) { readByte().toInt() != 0 }
}
fun readCharArray(arrayLength: Int): CharArray {
return CharArray(arrayLength) {
readChar()
}
}
fun readString(
byteCount: Int,
charset: Charset
): String {
bytesRead += byteCount
return source.readString(byteCount.toLong(), charset)
}
fun readFloatArray(arrayLength: Int): FloatArray {
return FloatArray(arrayLength) { readFloat() }
}
fun readDoubleArray(arrayLength: Int): DoubleArray {
return DoubleArray(arrayLength) { readDouble() }
}
fun readShortArray(arrayLength: Int): ShortArray {
return ShortArray(arrayLength) { readShort() }
}
fun readIntArray(arrayLength: Int): IntArray {
return IntArray(arrayLength) { readInt() }
}
fun readLongArray(arrayLength: Int): LongArray {
return LongArray(arrayLength) { readLong() }
}
fun readLong(): Long {
bytesRead += LONG_SIZE
return source.readLong()
}
fun readByte(): Byte {
bytesRead += BYTE_SIZE
return source.readByte()
}
fun readBoolean(): Boolean {
bytesRead += BOOLEAN_SIZE
return source.readByte()
.toInt() != 0
}
fun readByteArray(byteCount: Int): ByteArray {
bytesRead += byteCount
return source.readByteArray(byteCount.toLong())
}
fun readChar(): Char {
return readString(CHAR_SIZE, Charsets.UTF_16BE)[0]
}
fun readFloat(): Float {
return Float.fromBits(readInt())
}
fun readDouble(): Double {
return Double.fromBits(readLong())
}
fun readId(): Long {
// As long as we don't interpret IDs, reading signed values here is fine.
return when (identifierByteSize) {
1 -> readByte().toLong()
2 -> readShort().toLong()
4 -> readInt().toLong()
8 -> readLong()
else -> throw IllegalArgumentException("ID Length must be 1, 2, 4, or 8")
}
}
fun readUtf8(byteCount: Long): String {
bytesRead += byteCount
return source.readUtf8(byteCount)
}
fun readUnsignedShort(): Int {
return readShort().toInt() and 0xFFFF
}
companion object {
private val BOOLEAN_SIZE = BOOLEAN.byteSize
private val CHAR_SIZE = CHAR.byteSize
private val BYTE_SIZE = BYTE.byteSize
private val SHORT_SIZE = SHORT.byteSize
private val INT_SIZE = INT.byteSize
private val LONG_SIZE = LONG.byteSize
private val BOOLEAN_TYPE = BOOLEAN.hprofType
private val CHAR_TYPE = CHAR.hprofType
private val FLOAT_TYPE = FLOAT.hprofType
private val DOUBLE_TYPE = DOUBLE.hprofType
private val BYTE_TYPE = BYTE.hprofType
private val SHORT_TYPE = SHORT.hprofType
private val INT_TYPE = INT.hprofType
private val LONG_TYPE = LONG.hprofType
private const val INT_MASK = 0xffffffffL
private const val BYTE_MASK = 0xff
}
}