Java 异步接口运行之后会占用大量的服务器内存该怎么解决?

33 天前
 tiRolin

我的一个项目内提供了一个接口,这个接口使用异步实现,主要是做一大堆的请求并将请求到的数据更新到数据库里和记录日志,但是问题是,这个接口一旦调用并执行完毕之后,就会残留大量的字节数据在内存里且不会自动清除,大概是会产生 2G 左右的垃圾,在我的本地环境里我运行了好几次是没问题的,因为即使残留了 2G 后续也会运行会正确进行 GC 避免内存满了,我在本地环境里手动进行 GC 也是可以正确清除掉这些垃圾的

具体看下图,我运行接口之后产生了很多的字节数据没有处理

在服务器空间足够的情况下,也能够正常进行垃圾回收从而保证项目的正常运行

但是我的服务器就只有 4G ,还要运行其他的东西,这 2G 的垃圾堆在那我内存就所剩无几了,甚至都没办法正常调用其他接口,我又没办法扩充服务器,所以想要解决这个问题

我在本地是想着先用 System.gc()这个方法来让 JVM 进行 GC 的,作为一个暂时的解决方法,但是这个方法本地能正确进行 GC ,但是到服务器就压根没做这件事,内存没多出来多少

代码因为是我学校的代码,我不能全放出来,所以我就放一部分的关键的代码到下面,我个人认为问题应该也出在下面这段代码里 业务代码:

List<OrgApiManageExecuteVO> orgApiManageList = new ArrayList<>();
// 创建一个线程池
ThreadPoolExecutor tpe = new ThreadPoolExecutor(4, 10, 40, TimeUnit.SECONDS, new LinkedBlockingQueue<>(75));
//    ThreadPoolExecutor tpe = new ThreadPoolExecutor(30, 40, 40, TimeUnit.SECONDS, new LinkedBlockingQueue<>());

log.info("api 请求开始=======================");
Long startTime = System.currentTimeMillis();
for (OrgApiManage orgApiManageVo : testApiManageVos) {
    if (!StringUtils.hasText(orgApiManageVo.getFullClassName())) {
        continue;
    }
    String threadName = "org-" + orgApiManageVo.getId();
    Callable<OrgApiManageExecuteVO> c = new CallApiQueryCallable(orgApiManageVo, orgApiGroupManageMap, langTypeResult, threadName);
    // 执行任务并获取 Future 对象
    Future<OrgApiManageExecuteVO> f = tpe.submit(c);
    OrgFutureResult orgFutureResult = new OrgFutureResult();
    orgFutureResult.setOrgApiManageVo(orgApiManageVo);
    orgFutureResult.setF(f);
    orgFutureResult.setThreadName(threadName);
    results.add(orgFutureResult);
}
log.info("========= ExecuteListSize:{} ====", results.size());
// 关闭线程池
tpe.shutdown();
// 获取所有并发任务的运行结果
for (OrgFutureResult orgFutureResult : results) {
    Future<OrgApiManageExecuteVO> f = orgFutureResult.getF();
    if (f.isDone()) {
        OrgApiManageExecuteVO returnDTO = f.get();
        orgApiManageList.add(returnDTO);
    } else {
        log.info("=========== 任務未完成   最多等待 60 分钟 =======");
        try {
            OrgApiManageExecuteVO returnDTO = f.get(2, TimeUnit.MINUTES);
            orgApiManageList.add(returnDTO);
        } catch (InterruptedException | ExecutionException e) {
            errorMsg(orgApiManageList, orgFutureResult, e, "線程任務意外中斷");
            Thread.currentThread().interrupt();
        } catch (TimeoutException e) {
            errorMsg(orgApiManageList, orgFutureResult, e, "線程執行的任務超時");
        }
    }
}
log.info("======== return orgApiManageList Size:{} =====", orgApiManageList.size());
Long runTime = System.currentTimeMillis() - startTime;
log.info("======= CallApiQueryCallable runTime:{}ms", runTime);
return orgApiManageList;

这里的异步任务做的就是构造 URL 发送请求等待请求返回结果并进行相应的处理(比如记录或者更新),大概是这样的,只是请求的数量很多,每次请求过来的线程数有两百多个,我这小服务总是拖挺久才能搞定,然后积压一堆不知道哪里的 byte 数据在内存里还清除不掉

我因为这个问题已经困扰了两天了,我个人尝试了很多办法都没能解决,所以我来问问各位,希望有大佬能不吝赐教,或者告诉我一些简单的思路也可以,我会自己去尝试的,先谢谢各位了

3268 次点击
所在节点    Java
44 条回复
cvbnt
33 天前
试试 spring webclient
yuaotian
33 天前
1 、任务二次分发,分成更小任务去处理
2 、流关闭要绝对放在 finally 里面或使用 try-with-resources 处理流
3 、建议重构并发任务那一块,没有具体代码,但是感觉告诉我,重构一下会解决。
palfortime
33 天前
Xmx 设置了多少
letianqiu
33 天前
你确定你本地 jvm 的启动参数和 server 是一样的吗。System.gc()不起作用很有可能是在参数里 disableexplicitgc 了
Badlink
33 天前
需要 60 分钟超时时间这么久吗, 缩短看看呢?
orioleq
33 天前
线程池要等所有任务结束了才能关闭…
ming159
33 天前
orgApiManageList 最后释放了吗?
LiaoMatt
33 天前
在方法中新建了线程池吗
tiRolin
33 天前
layxy
33 天前
用的哪个 http 客户端
RipperJack666
33 天前
对象创建问题
yazinnnn0
33 天前
1. 用响应式的框架+kotlin 协程
2. 用 jdk21+虚拟线程
palfortime
33 天前
@tiRolin 设置 1g ,是怎么跑到 2g 的,堆外内存吗? http 请求的时候用了 netty ? http 请求用了什么库?一次请求的报文有多大?
kandaakihito
33 天前
jdk8 以及 jdk17 ,遇到的情况一模一样。

多线程跑完任务之后,一堆东西在内存里面死活 GC 不掉(资源什么的已经释放了)。而且和线程数量什么的没关系,纯粹就是运算量越大垃圾越多。堆内存也没法调小,一小就爆。

后面想了个骚操作来缓解,那就是尽可能把对象 new 在 for 循环里头,并且运算结果之类的数据全往 redis 塞,把 redis 当堆内存用(
cheng6563
33 天前
没 OOM 说明你内存没满也不需要 GC
hidemyself
33 天前
tpe 这样定义没问题吗。。
vagusss
33 天前
每次请求都 new ThreadPoolExecutor 这不对吧
futaotao5866
33 天前
ThreadPoolExecutor tpe = new ThreadPoolExecutor(4, 10, 40, TimeUnit.SECONDS, new LinkedBlockingQueue<>(75));每次进入方法都会执行这个,这个放在方法外面

// 关闭线程池
tpe.shutdown();
为啥要关闭,不理解,可以去掉
jov1
33 天前
看起来线程池在业务方法里面每次创建吗,这个建议放全局,然后如果要等一批任务异步执行完,可以这样
```
List<CompletableFuture<String>> futures = inputs.stream()
.map(input -> CompletableFuture.supplyAsync(() -> process(input), executorService))
.collect(Collectors.toList());

// 等待所有任务完成并收集结果
return futures.stream().map(CompletableFuture::join).collect(Collectors.toList());
```
或者还是用线程池,但是用 CountDownLatch 等待任务执行都可以。
clf
33 天前
本地可以,云端不行?是有用容器之类的部署么。。。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1094804

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX