上周订单导出功能重构我想着用AI提速就把需求丢给了Cursor——把原来for循环拼接的逻辑改成Stream并行处理。AI写得很快三分钟就给我吐了一段看起来很优雅的parallelStream代码我还觉得挺满意review了一遍就合了。上线第二天凌晨告警就来了。订单服务堆内存飙到95%GC根本回收不掉。翻日志一看全是Java heap space导出接口超时率从0.3%直接干到了12%。问题出在哪AI给我的代码大概长这样java ListOrderExportDTO result orders.parallelStream() .map(order - enrichOrderDetail(order)) // 每个订单查3次远程接口 .map(dto - calculatePrice(dto)) // 价格计算涉及BigDecimal运算 .collect(Collectors.toList());看起来没毛病对吧但这里藏了两个坑。第一个坑是parallelStream的默认线程池。AI压根没提这茬——parallelStream用的是ForkJoinPool.commonPool()这个池子的大小等于CPU核心数-1。我那台8核的机器只有7个工作线程而导出请求一秒能来二三十个。七个人干活二三十个任务排队全堆在内存里等着。第二个坑更隐蔽。enrichOrderDetail方法里调了三个远程接口每个接口平均耗时200ms。parallelStream的工作线程被远程调用阻塞在那ForkJoinPool的任务队列疯狂堆积。原来for循环虽然慢但至少是逐条处理不会把几万条数据同时展开到内存里。改了Stream之后反而是快出了问题——所有数据同时进入处理流水线内存占用直接翻了十几倍。我是怎么排查的说实话第一反应是怀疑远程接口变慢了。翻了一轮监控接口RT没变化。然后我jmap了一把看到内存里全是CompletableFuture和ForkJoinTask对象才反应过来是并行流搞的鬼。jstack看线程状态更直观——7个ForkJoinPool工作线程全部停在WAITING状态等远程调用返回。而主线程在LinkedBlockingQueue.take()上阻塞等着collect完成。java // jstack关键信息 ForkJoinPool.commonPool-worker-3 - WAITING on java.util.concurrent.CompletableFuture ForkJoinPool.commonPool-worker-5 - WAITING on java.util.concurrent.CompletableFuture http-nio-8080-exec-12 - WAITING on java.util.concurrent.LinkedBlockingQueue.take()修复方案不要在parallelStream里做IO密集型操作这应该是个常识但AI不会主动告诉你。它只负责把代码写出来不管你的业务场景适不适合。我改回for循环了没有。换了个思路——用自定义线程池 CompletableFuture组合把并行度和IO隔离开java ExecutorService exportPool new ThreadPoolExecutor( 4, 4, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue(100), // 限制队列长度别让任务堆积 new ThreadPoolExecutor.CallerRunsPolicy() // 队列满了主线程自己跑 );List futures orders.stream() .map(order - CompletableFuture.supplyAsync( () - enrichOrderDetail(order), exportPool )) .collect(Collectors.toList());List result futures.stream() .map(CompletableFuture::join) .collect(Collectors.toList()); 关键改动三个地方一是用了独立线程池不再跟ForkJoinPool抢资源二是ArrayBlockingQueue限制队列长度100超出就CallerRunsPolicy回退到同步执行三是CompletableFuture和Stream分开避免嵌套的并行流导致任务展开失控。改完上线堆内存稳在40%以下导出接口超时率回到0.2%。这事给我的教训AI写Stream代码很快但它不会帮你评估业务场景。parallelStream适合CPU密集型的纯计算任务比如数据排序、集合过滤。一旦里面混了远程调用、数据库查询这种IO操作它就是定时炸弹。以后用AI生成并发代码我多留个心眼先看有没有IO操作再看线程池是谁的最后看有没有背压机制。AI给的答案永远是能不能跑跑得稳不稳得自己掂量。还有一个细节——AI生成的代码里没有做异常隔离。enrichOrderDetail如果抛了异常parallelStream的整个管道会直接挂掉连部分结果都拿不到。我后来加了try-catch包了一层异常订单单独记录到失败列表至少保证正常订单能导出来。这种边界情况AI几乎不会主动处理它只管happy path。你的线上环境可没有happy path。
让AI帮我写Java Stream,差点把线上内存搞崩了
上周订单导出功能重构我想着用AI提速就把需求丢给了Cursor——把原来for循环拼接的逻辑改成Stream并行处理。AI写得很快三分钟就给我吐了一段看起来很优雅的parallelStream代码我还觉得挺满意review了一遍就合了。上线第二天凌晨告警就来了。订单服务堆内存飙到95%GC根本回收不掉。翻日志一看全是Java heap space导出接口超时率从0.3%直接干到了12%。问题出在哪AI给我的代码大概长这样java ListOrderExportDTO result orders.parallelStream() .map(order - enrichOrderDetail(order)) // 每个订单查3次远程接口 .map(dto - calculatePrice(dto)) // 价格计算涉及BigDecimal运算 .collect(Collectors.toList());看起来没毛病对吧但这里藏了两个坑。第一个坑是parallelStream的默认线程池。AI压根没提这茬——parallelStream用的是ForkJoinPool.commonPool()这个池子的大小等于CPU核心数-1。我那台8核的机器只有7个工作线程而导出请求一秒能来二三十个。七个人干活二三十个任务排队全堆在内存里等着。第二个坑更隐蔽。enrichOrderDetail方法里调了三个远程接口每个接口平均耗时200ms。parallelStream的工作线程被远程调用阻塞在那ForkJoinPool的任务队列疯狂堆积。原来for循环虽然慢但至少是逐条处理不会把几万条数据同时展开到内存里。改了Stream之后反而是快出了问题——所有数据同时进入处理流水线内存占用直接翻了十几倍。我是怎么排查的说实话第一反应是怀疑远程接口变慢了。翻了一轮监控接口RT没变化。然后我jmap了一把看到内存里全是CompletableFuture和ForkJoinTask对象才反应过来是并行流搞的鬼。jstack看线程状态更直观——7个ForkJoinPool工作线程全部停在WAITING状态等远程调用返回。而主线程在LinkedBlockingQueue.take()上阻塞等着collect完成。java // jstack关键信息 ForkJoinPool.commonPool-worker-3 - WAITING on java.util.concurrent.CompletableFuture ForkJoinPool.commonPool-worker-5 - WAITING on java.util.concurrent.CompletableFuture http-nio-8080-exec-12 - WAITING on java.util.concurrent.LinkedBlockingQueue.take()修复方案不要在parallelStream里做IO密集型操作这应该是个常识但AI不会主动告诉你。它只负责把代码写出来不管你的业务场景适不适合。我改回for循环了没有。换了个思路——用自定义线程池 CompletableFuture组合把并行度和IO隔离开java ExecutorService exportPool new ThreadPoolExecutor( 4, 4, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue(100), // 限制队列长度别让任务堆积 new ThreadPoolExecutor.CallerRunsPolicy() // 队列满了主线程自己跑 );List futures orders.stream() .map(order - CompletableFuture.supplyAsync( () - enrichOrderDetail(order), exportPool )) .collect(Collectors.toList());List result futures.stream() .map(CompletableFuture::join) .collect(Collectors.toList()); 关键改动三个地方一是用了独立线程池不再跟ForkJoinPool抢资源二是ArrayBlockingQueue限制队列长度100超出就CallerRunsPolicy回退到同步执行三是CompletableFuture和Stream分开避免嵌套的并行流导致任务展开失控。改完上线堆内存稳在40%以下导出接口超时率回到0.2%。这事给我的教训AI写Stream代码很快但它不会帮你评估业务场景。parallelStream适合CPU密集型的纯计算任务比如数据排序、集合过滤。一旦里面混了远程调用、数据库查询这种IO操作它就是定时炸弹。以后用AI生成并发代码我多留个心眼先看有没有IO操作再看线程池是谁的最后看有没有背压机制。AI给的答案永远是能不能跑跑得稳不稳得自己掂量。还有一个细节——AI生成的代码里没有做异常隔离。enrichOrderDetail如果抛了异常parallelStream的整个管道会直接挂掉连部分结果都拿不到。我后来加了try-catch包了一层异常订单单独记录到失败列表至少保证正常订单能导出来。这种边界情况AI几乎不会主动处理它只管happy path。你的线上环境可没有happy path。