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

使用Akka与Java读取大量含JSON记录文件时出现内存溢出错误

解决Akka Actor处理大JSON文件时的OutOfMemoryError问题

嘿,这个内存溢出的坑我踩过好多次了!你现在的问题根源太明显:你把整个JSON文件一次性全加载到内存里了——几千个文件每个又包含几千条记录,所有JSON对象都堆在内存里,JVM的堆内存肯定扛不住啊!咱们一步步来优化:

1. 立刻换掉一次性解析,用流式JSON解析

别再用JSONParser把整个文件转成JSONArray了,这种方式会把所有JSON对象都留在内存里,哪怕你在迭代发送,这些对象也可能因为被迭代器或其他引用持有而无法被GC回收。

推荐用Jackson的Streaming API(或者Gson的JsonReader),它能逐条读取JSON记录,解析一条处理一条,完全不用把整个数组加载到内存。给你个实用的代码例子:

// 先确保引入Jackson的core和databind依赖
ObjectMapper objectMapper = new ObjectMapper();

try (JsonParser jsonParser = objectMapper.getFactory().createParser(new File(file))) {
    // 跳过JSON数组的开头符号「[」
    jsonParser.nextToken();
    
    // 循环读取直到数组结束「]」
    while (jsonParser.nextToken() != JsonToken.END_ARRAY) {
        // 逐条解析成JSONObject(换成自定义POJO类更省内存)
        JSONObject currentJsonObject = objectMapper.readValue(jsonParser, JSONObject.class);
        // 发送给目标Actor
        targetActor.tell(currentJsonObject, ActorRef.noSender());
    }
} catch (IOException e) {
    // 记得处理IO或解析异常,比如记录日志、通知监控
    e.printStackTrace();
}

如果你的JSON是每行一条记录(不是数组包裹的),直接按行读取解析会更简单。

2. 用Akka Stream代替纯Actor处理,自带背压更省心

Akka Stream专门为批量流式处理场景设计,自带背压机制——当处理Actor跟不上速度时,数据源会自动暂停发送,彻底避免内存堆积。比如用Akka Stream读取文件、解析JSON并发送给Actor:

// 假设已初始化Akka系统和处理Actor的引用
ActorSystem system = ActorSystem.create("JsonProcessingSystem");
ActorRef processorActor = system.actorOf(ProcessorActor.props(), "processor");

// 构建流处理逻辑
Source.fromFile(new File(file))
    // 解析JSON数组,拆分成单个JsonNode
    .via(JsonParsing.jsonArray())
    .mapConcat(JsonParsing::parseArray)
    // 转换为你需要的JSONObject(直接用JsonNode处理更高效)
    .map(jsonNode -> new JSONObject(jsonNode.toString()))
    // 用带确认的Sink发送给Actor,实现背压
    .runWith(
        Sink.actorRefWithAck(
            processorActor,
            new InitMessage(),  // 初始化消息
            new AckMessage(),   // 处理完成后的确认消息
            new DoneMessage()   // 流结束的消息
        ),
        system
    );

这种方式不仅内存占用极低,还能自动控制处理速度,比纯Actor手动管理并发靠谱多了。

3. 辅助优化:控制Actor并发数 + 临时调整JVM参数

  • 控制并发:别一下子启动太多Actor处理文件,比如用Akka的Router(比如RoundRobinPool)限制同时处理的文件数,比如设置nr-of-instances = 10,避免同时加载几十个文件到内存。
  • JVM参数临时应急:如果暂时没法改代码,可以先加大堆内存救急,比如启动参数加-Xmx4g(把堆内存调到4G),但这只是治标不治本的办法,最终还是要靠流式解析解决。

为什么原来的代码会OOM?

你原来的代码里,parser.parse(new FileReader(file))会把整个文件内容转换成JSONArray,所有JSONObject都存放在这个数组里。哪怕你在循环里发送消息,这些对象依然被数组和迭代器引用,GC无法回收,随着文件越来越多,内存被占满就会抛出OutOfMemoryError。


内容的提问来源于stack exchange,提问作者riorio

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 06:30:23