使用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
相关产品推荐
相关产品推荐

