MAT is a tool to programmatically work with java heap dumps. While there are many tools for analyzing heap dumps, most of them do not have an API to pull out and work with the objects in the heap direclty. MAT lets you do that. See the extended example below.
mvn clean install
that will publish to your local maven repo, which then you can include in another project using:
<dependency>
<groupId>mat</groupId>
<artifactId>mat-core</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
This code was originally pulled directly from https://bitbucket.org/vshor/mat (sorry I converted to git from
mercurial and didn't keep the history because, well, I'm lazy). It was actually discovered from
https://github.com/square/haha, but that only works for Android heap dumps. vshor/mat was in turn
taken from the Eclipse Memory Analyzer Tool (MAT), but that is unusable outside
of Eclipse.
My original use case was when I wanted to work with the objects in a heap dump. Though I could browse the objects in YourKit, I couldn't work with them programmatically. In particular, the heap dump I was working with had lots of 80 MB buffers, and I had no idea what was in them. A coworker suggested I look for ascii strings in the buffers to see if that helped shed light on their contents. I could browse bits of the buffer graphically in YourKit -- but I couldn't do a comprehensive search over 80 MB.
But using MAT, I could pull the byte arrays out of the heap dump, and then work with them directly. Here's an example in scala:
importjava.io.Fileimportscala.collection.JavaConverters._importorg.eclipse.mat.util.IProgressListenerimportorg.eclipse.mat.parser.internal.SnapshotFactoryimportorg.eclipse.mat.util.IProgressListener.Severityimportorg.eclipse.mat.snapshot.model.IPrimitiveArrayvalfactory=newSnapshotFactory()
valpath="..."vallistener=newIProgressListener {
overridedefsendUserMessage(severity: Severity, s: String, throwable: Throwable):Unit= {
println(s"$severity: $s$throwable")
}
overridedefisCanceled:Boolean=falsevarworkedTotal=0overridedefdone():Unit= {
println("done")
}
overridedefworked(i: Int):Unit= {
workedTotal += i
println(s"worked $i (total = $workedTotal)")
}
overridedefsetCanceled(b: Boolean):Unit= {}
overridedefsubTask(s: String):Unit= {
println(s"subtask $s")
}
overridedefbeginTask(s: String, i: Int):Unit= {
println(s"Beginning ${s} with $i units")
}
}
valstart=System.currentTimeMillis()
valsnapshot= factory.openSnapshot(newFile(path), new java.util.HashMap(), listener)
valend=System.currentTimeMillis()
println(s"heap loaded in ${(end - start) /1000}s")
valclsName=newArray[Byte](0).getClass().getCanonicalName()
valicls= snapshot.getClassesByName(clsName, false).asScala.head
valbyteArrayIds= icls.getObjectIds
valbigByteArrayId= byteArrayIds.maxBy{id => snapshot.getHeapSize(id)}
valbigByteArray= snapshot.getObject(bigByteArrayId).asInstanceOf[IPrimitiveArray]
valarr= bigByteArray.getValueArray.asInstanceOf[Array[Byte]]
/** * find runs of bytes that might be ascii characters and print them out to see * if they might be meaningful strings*/deffindStrings(bytes: Array[Byte], minLength: Int=8):Unit= {
varidx=0varinPrintable=falsevarprintableBegin=-1while (idx < bytes.length) {
valprintable= (bytes(idx) >=32&& bytes(idx) <127)
if (printable &&!inPrintable) {
inPrintable =true
printableBegin = idx
} elseif (!printable && inPrintable) {
if (idx - printableBegin >= minLength) {
valstr=newString(bytes.slice(printableBegin, idx).map{_.toChar})
println(s"""printable from ${printableBegin} to ${idx}: "$str"""")
}
inPrintable =false
}
idx +=1
}
}
findStrings(arr)