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

1 天前
 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 数据在内存里还清除不掉

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

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

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

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

// 关闭线程池
tpe.shutdown();
为啥要关闭,不理解,可以去掉
jov1
17 小时 23 分钟前
看起来线程池在业务方法里面每次创建吗,这个建议放全局,然后如果要等一批任务异步执行完,可以这样
```
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
17 小时 6 分钟前
本地可以,云端不行?是有用容器之类的部署么。。。

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

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

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

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

© 2021 V2EX