Json转String/Bytes生成ByteArrayInputStream时抛出OOM问题咨询
根因分析
你当前的OOM本质是全量缓存大对象导致的:代码执行过程中会在堆内生成3份和JSON体积等大的对象副本:
StringBuilder中存储的格式化后JSON字符序列jsonString.toString()生成的完整String对象getBytes()生成的完整byte数组
再加上ByteArrayInputStream本身会持有全量byte数组的引用,只要JSON体积接近堆内存上限,就会在数组拷贝阶段触发OOM。只要你还在走「先生成完整JSON字符串,再转成流写入」的逻辑,不管怎么调整读byte[]的方式,都绕不开全量对象的内存开销。
最优方案:流式序列化直接写入输出流
不要先生成完整的格式化JSON字符串,直接让JSON序列化器对接你的目标输出流tmpBufferedOutputStream,全程内存仅持有KB级别的序列化缓冲区,内存占用和JSON总大小完全无关,从根源上避免大对象OOM。
你代码里用的PrettyParams是json4s库的配置,该库原生支持流式写入,改造后的代码如下:
import org.json4s._ import java.io.OutputStreamWriter import java.nio.charset.StandardCharsets val writer = new OutputStreamWriter(tmpBufferedOutputStream, StandardCharsets.UTF_8) try { val formatConfig = PrettyParams.nospace.copy(preserveOrder = true) // 直接将JSON对象渲染后逐块写入输出流,不生成全量中间字符串 JsonMethods.render(json, formatConfig).writeTo(writer) writer.flush() tmpBufferedOutputStream.flush() } catch { case e: Exception => e.printStackTrace() throw e } finally { writer.close() }
这种方案下,序列化器会把JSON拆成小块逐段写出,哪怕JSON体积达到上百GB,只要磁盘空间足够就能正常写入,是处理大JSON的标准方案。
过渡方案:分片写入(仅适合无法修改序列化逻辑的场景)
如果你暂时无法替换现有生成formatjson字符串的逻辑,可以放弃ByteArrayInputStream和全量转byte[]的逻辑,将字符串按固定大小分片转码后直接写入输出流,至少能减少2份全量副本的内存开销:
import java.nio.charset.StandardCharsets val chunkSize = 8192 // 8KB分片,单块内存占用极低 val totalLength = formatjson.length var cursor = 0 val charset = StandardCharsets.UTF_8 while (cursor < totalLength) { val segmentEnd = Math.min(cursor + chunkSize, totalLength) // 每次仅处理当前分片的字符,转码后直接写出 val segmentBytes = formatjson.substring(cursor, segmentEnd).getBytes(charset) tmpBufferedOutputStream.write(segmentBytes) cursor = segmentEnd } tmpBufferedOutputStream.flush()
注意:该方案依然会在内存中持有完整的formatjson字符串,如果JSON体积大到单份字符串就能撑爆堆内存,该方案依然会OOM,仅作为临时过渡使用,优先选择流式序列化方案。
避坑说明
- 不要继续使用
ByteArrayInputStream处理大内容:该类的实现要求传入完整的byte数组,本质还是全量内存缓存,无法解决大对象OOM问题 - 不要对大字符串直接调用
getBytes():该方法会申请和字符串等大的连续内存空间,大体积下极易触发堆内存不足 - 分片处理时不要直接按byte长度切分原始字符串:UTF-8编码下多字节字符存在跨分片的可能,按字符切分后逐段转码可以避免乱码问题
内容的提问来源于stack exchange,提问作者Sathish Kumar S
相关产品推荐
相关产品推荐

