如何从Java堆转储中提取ConcurrentSkipListMap存储的byte[]原始字节数组?
提取堆转储中ConcurrentSkipListMap的字节数组并反序列化为Protobuf对象的方法
方法一:MAT OQL查询+自定义脚本导出
先定位目标Map
打开MAT的OQL编辑器,输入查询语句找到InMemoryWindowStore下的所有ConcurrentSkipListMap实例:SELECT map FROM org.apache.kafka.streams.state.internals.InMemoryWindowStore store JOIN store.windowStore AS map WHERE INSTANCEOF(map, java.util.concurrent.ConcurrentSkipListMap)执行后会列出所有符合条件的Map,选中你要分析的那个,记下它的对象ID(MAT里每个对象都有唯一ID,在详情面板能找到)。
提取Map中的所有entry
把刚才的对象ID代入下面的OQL,获取所有键值对:SELECT entry.key, entry.value FROM INSTANCEOF(com.sun.proxy.$ProxyXX, java.util.Map.Entry) entry WHERE entry.owner == <你的Map对象ID>注意替换
<你的Map对象ID>为实际的ID值。导出字节数组到文件
MAT支持用Groovy脚本批量处理数据,直接写个脚本遍历查询结果,把每个value的byte[]写入文件,还可以加个分隔符区分不同条目:def outputFile = new File("/tmp/protobuf_data.bin") def entries = query("SELECT entry.value FROM INSTANCEOF(com.sun.proxy.$ProxyXX, java.util.Map.Entry) entry WHERE entry.owner == 0x12345678") entries.eachWithIndex { entry, index -> def bytes = entry.value outputFile.append(bytes) // 加个分隔符,方便后续拆分 outputFile.append("\n---ENTRY_$index_END---\n") }把
0x12345678换成你的Map对象ID,分隔符可以根据自己的需求调整。
方法二:编写MAT自定义插件(适合百万级数据)
如果300万条数据用OQL查起来慢,可以直接写MAT插件,绕过OQL直接操作堆内存:
- 用MAT提供的
IObjectInspectorAPI,直接访问ConcurrentSkipListMap的内部table数组,遍历每个节点的value字段。 - 读取byte[]后,直接调用Protobuf的反序列化方法转成对象,再保存成JSON或者其他易读格式。
- 核心代码片段(Java):
这种方法效率高,适合处理大数据量。// 获取目标Map的inspector IObjectInspector mapInspector = heap.getObjectInspector(mapObjectId); // 取ConcurrentSkipListMap内部的table数组 IObjectReference tableRef = (IObjectReference) mapInspector.getFieldValue("table"); IArrayInstance tableArray = (IArrayInstance) heap.getObject(tableRef.getObjectId()); // 遍历每个节点 for (long nodeId : tableArray.getReferenceArray()) { if (nodeId == 0) continue; IObjectInspector nodeInspector = heap.getObjectInspector(nodeId); // 取出value对应的byte[] IObjectReference valueRef = (IObjectReference) nodeInspector.getFieldValue("value"); IArrayInstance byteArray = (IArrayInstance) heap.getObject(valueRef.getObjectId()); byte[] rawBytes = byteArray.getPrimitiveArray(); // 反序列化为Protobuf对象 YourProtobufProto.YourProtobufObject obj = YourProtobufProto.YourProtobufObject.parseFrom(rawBytes); // 把对象写入文件,比如转成JSON // ... }
方法三:MAT命令行批量处理
如果GUI模式卡得厉害,直接用MAT的命令行模式跑脚本:
./mat/ParseHeapDump.sh your-heap-dump.hprof -script extract_bytes.groovy
脚本内容和方法一里的Groovy脚本一样,后台跑就行,不用盯着GUI。
注意点
- 处理300万条数据时,记得调MAT的内存参数,修改
MemoryAnalyzer.ini里的-Xmx,比如改成-Xmx16g,避免内存不够崩溃。 - 导出字节数组时,要么加清晰的分隔符,要么每个条目单独存小文件,不然后续拆分起来麻烦。
- 反序列化一定要用正确的
.proto文件生成Java类,不然会报错。
内容的提问来源于stack exchange,提问作者Kohei Nozaki
相关产品推荐
相关产品推荐

