You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java中使用ScheduledExecutorService触发OOM,求正确使用方式

ScheduledExecutorService OOM & 持续Full GC问题分析与正确用法

Hey there! Let's walk through what's causing your OutOfMemoryError and those endless Full GC logs, plus cover the right way to use ScheduledExecutorService.

1. 为什么会出现OOM和持续Full GC?

你设置的JVM堆内存太小了:-Xms5m -Xmx5m只给了5MB的堆空间。现代Java应用哪怕只是启动基础类、线程池、任务调度框架,都需要远超这个大小的内存。当堆被占满后,JVM会不断触发Full GC试图回收空间,但几乎没什么可回收的(你的任务本身很轻,但框架和系统开销已经占满了堆),所以会陷入"GC循环",最后抛出OutOfMemoryError。

看你的GC日志:5099K->5099K(5632K)意味着Full GC后几乎没释放任何内存,堆已经被完全占满了。

2. 你的代码里的小问题(不直接导致OOM,但影响逻辑)

  • 拼写错误:jobNmae应该是jobName(虽然不影响运行,但代码规范要注意)
  • scheduleWithFixedDelay的参数错误:你传入的是period(2秒)而不是period1(4秒),这会导致job2的执行间隔变成2秒,和job1一样,不符合你的预期。

3. ScheduledExecutorService的正确使用方式

a. 合理配置JVM内存

先把堆内存调大,比如改成:

java -Xms256m -Xmx256m -XX:+PrintGC ScheduleExecutorTest > ./output.txt

这个大小足够支撑你的测试任务,实际生产环境可以根据业务需求调整。

b. 手动创建线程池(比Executors更可控)

Executors.newScheduledThreadPool()是快捷方式,但缺乏自定义配置。建议直接用ScheduledThreadPoolExecutor构造器,指定线程池大小、拒绝策略等,避免资源耗尽:

// 自定义线程池,指定核心线程数,还可以配置线程工厂、拒绝策略
ScheduledExecutorService service = new ScheduledThreadPoolExecutor(
    10,
    Executors.defaultThreadFactory(),
    new ThreadPoolExecutor.AbortPolicy() // 任务满时直接抛出异常,也可以选其他策略
);

c. 确保任务执行时间不超过间隔(针对scheduleAtFixedRate)

scheduleAtFixedRate是固定频率执行,如果任务执行时间超过了period,任务会堆积在队列里,占用内存。如果你的任务可能耗时较长,建议用scheduleWithFixedDelay(固定延迟,上一个任务结束后再等delay执行下一个),或者确保任务执行时间远小于间隔。

d. 任务结束后关闭线程池

如果是临时任务(比如你的测试代码),记得在合适的时机关闭线程池,不然JVM会一直运行(线程池的非守护线程会阻止JVM退出):

// 比如测试时,运行一段时间后关闭
Thread.sleep(30000); // 运行30秒
service.shutdown();
// 等待所有任务完成,或者超时
if (!service.awaitTermination(10, TimeUnit.SECONDS)) {
    service.shutdownNow();
}

4. 修改后的完整测试代码

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ScheduleExecutorTest implements Runnable {
    private String jobName = "";

    public ScheduleExecutorTest(String jobName) {
        super();
        this.jobName = jobName;
    }

    @Override
    public void run() {
        System.out.println("execute : " + jobName);
    }

    public static void main(String[] args) throws InterruptedException {
        // 自定义线程池,更可控
        ScheduledExecutorService service = Executors.newScheduledThreadPool(10);

        long initialDelays = 4;
        long period = 2;
        // 固定频率执行:初始延迟4秒,之后每2秒执行一次
        service.scheduleAtFixedRate(new ScheduleExecutorTest("job1"), initialDelays, period, TimeUnit.SECONDS);

        long initialDelays1 = 4;
        long period1 = 4;
        // 固定延迟执行:初始延迟4秒,上一个任务结束后等待4秒执行下一个
        service.scheduleWithFixedDelay(new ScheduleExecutorTest("job2"), initialDelays1, period1, TimeUnit.SECONDS);

        // 测试运行30秒后关闭线程池
        Thread.sleep(30000);
        service.shutdown();
        if (!service.awaitTermination(10, TimeUnit.SECONDS)) {
            service.shutdownNow();
        }
    }
}

5. 补充:GC日志说明

那些[Full GC (Ergonomics)]日志是JVM的GC自适应调节机制触发的,当JVM检测到堆内存不足时,会自动触发Full GC尝试回收。但因为你的堆太小了,回收后几乎没有可用空间,所以会反复触发,直到最后抛出OOM。

内容的提问来源于stack exchange,提问作者yue mu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 04:24:16