You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何从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提供的IObjectInspector API,直接访问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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 20:38:20