Java使用GSON Streaming API解析超大JSON仍报OutOfMemoryError求助
超大JSON流式解析OutOfMemoryError问题优化方案
根本原因排查
你虽然用了GSON流式解析避免了全量JSON加载到内存,但解析后的全量业务数据依然全部驻留在堆内存中,是触发OOM的核心原因,具体问题点如下:
- 全量解析结果存储在嵌套HashMap
finalcycleTimeMap中,数据量级超过160万条后,HashMap的哈希表开销+存储的业务数据总大小超过了JVM堆内存上限 - 代码存在多处不必要的序列化操作:每次循环都创建新的Gson实例序列化CycleTime对象打印,最后还会将全量结果Map序列化后打印,序列化生成的大字符串会临时占用大量堆内存
- 解析逻辑存在缺陷:CycleTime对象全局只创建1次反复复用,且
endObject存在重复调用的问题,可能导致解析逻辑错位,额外加载无效数据到内存 - 未显式释放IO资源:InputStream、JsonReader用完后未关闭,可能产生资源泄漏占用内存
优化方案
- 避免全量数据驻留内存:如果业务允许边解析边处理,解析单条CycleTime数据后直接执行业务逻辑(写入数据库、发送消息、实时计算等),不要将所有结果攒到Map中统一返回,从根源上解决内存占用问题
- 移除冗余序列化操作:删除最后全量打印Map的序列化代码,循环中的对象打印仅输出必要关键字段,Gson实例改为单例全局复用,不要每次循环创建新实例
- 修复解析逻辑缺陷:
- 每解析一条Report_Entry数组元素就创建一个新的CycleTime对象,避免脏数据和意外的内存持有
- 修正JsonReader的开闭逻辑:遍历数组元素时直接固定执行
beginObject,读取完所有字段后再执行endObject,不要重复调用
- 必须全量返回结果的场景优化:
- 调整JVM堆参数:根据数据量级放大最大堆内存,例如
-Xmx8G -Xms8G - 优化存储结构:将嵌套HashMap替换为更省内存的结构,比如直接存储CycleTime POJO,或者使用fastutil等内存优化的集合类,减少哈希表的额外开销
- 分页请求:如果上游接口支持分页,将单次全量请求拆分为多次分页请求,每次处理完一页数据后释放对应内存
- 调整JVM堆参数:根据数据量级放大最大堆内存,例如
- 资源自动释放:使用try-with-resources语法管理InputStream和JsonReader,解析完成后自动关闭资源
修正后的核心解析代码示例
// 全局复用Gson实例 private static final Gson GSON = new Gson(); // 解析方法使用try-with-resources自动关闭资源 public void parseCycleTime(InputStream inputStream, CycleTimeConstant cycleTimeConstant, int processId) throws IOException { try (JsonReader reader = new JsonReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { reader.beginObject(); while (reader.hasNext()) { String name = reader.nextName(); if ("Report_Entry".equals(name)) { reader.beginArray(); int counter = 0; while (reader.hasNext()) { // 每条数据新建对象 CycleTime cycleTime = new CycleTime(); reader.beginObject(); while (reader.hasNext()) { String fieldName = reader.nextName(); switch (fieldName) { case "key1": cycleTime.setKey1(reader.nextString()); break; case "key2": cycleTime.setKey2(reader.nextString()); break; case "key3": cycleTime.setKey3(reader.nextString()); break; case "key4": cycleTime.setKey4(reader.nextString()); break; case "key5": cycleTime.setKey5(reader.nextString()); break; default: // 跳过不需要的字段 reader.skipValue(); } } reader.endObject(); counter++; // 边解析边处理,不要攒到Map里 processSingleCycleTime(cycleTime, cycleTimeConstant, counter, processId); // 仅打印必要信息 if (counter % 10000 == 0) { System.out.println("已解析条数:" + counter); } } reader.endArray(); } else { // 跳过根节点下不需要的字段 reader.skipValue(); } } reader.endObject(); } } // 单条数据业务处理逻辑 private void processSingleCycleTime(CycleTime cycleTime, CycleTimeConstant cycleTimeConstant, int counter, int processId) { // 实现你的业务逻辑,比如写库、计算等 }
内容的提问来源于stack exchange,提问作者Kunal
相关产品推荐
相关产品推荐

