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

2024 年 12 月 4 日
 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 数据在内存里还清除不掉

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

7460 次点击
所在节点    Java
44 条回复
chihiro2014
2024 年 12 月 4 日
@blackmamba24 一般的做法不是创建全局线程池,然后共享么=。=。。。他这个方法结束了,线程池也 g
sagaxu
2024 年 12 月 4 日
1. JVM 版本未知,从 Java 11 开始,G1 GC 之后才可能把内存归还给 OS ,从 OS 层面不一定能观测到释放内存。

2. 缺乏 GC 日志,堆和堆外内存使用状况,必须结合 GC 日志才能分析,这是关键中的关键。

3. “甚至都没办法正常调用其他接口”,从描述看是 GC 也回收不了,有长生命周期变量持有废弃数据?

4. 观测一下线程数量,是否有太多线程没有正常结束。

5. 线程池要复用,不要老去创建新的。
tiRolin
2024 年 12 月 5 日
@yuaotian 谢谢了,我的问题已经解决了,其实跟内存无关,是跟 CPU 资源有关,内存是可以正确回收的,CPU 资源被占用了太多导致无法访问,实在不好意思,我的思路错误了所以我一楼写的内容也错误了,导致给大伙们带来了错误的思路
blackmamba24
2024 年 12 月 25 日
@chihiro2014 正解

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

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

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

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

© 2021 V2EX