为什么在线程池中执行大量 Runnable 时内存占用增加?

Why memory away increasing when execute huge number Runnable in thread pool?

我的同事给了我这样的代码片段:

public class Main1 {

        public static void main(String[] args) throws Exception {
            ExecutorService executorService = Executors.newFixedThreadPool(3);

            Runnable runnable = () -> {
                try {
                    // business logic
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(LocalDateTime.now().toString() + ":" + Thread.currentThread().getName());
            };

            for (int i = 0; i < 100000; i++) {
                executorService.execute(runnable);
            }

            System.in.read();

        }

    }

在上面的代码中,创建了 100000 个 Runnable 实例。当我 运行 这段代码时,我可以看到 JVisualVM 中的堆在增加,但是 100000 个 Runnable 实例的内存大小几乎没有变化。我的 JVM 选择是 -Xms20m -Xmx20m -XX:MaxTenuringThreshold=1。 Java 版本为 1.8.0_151,在 macOS High Sierra 版本 10.13.6 上。线程池中的所有线程都在休眠,那么为什么堆会增加呢?创建了什么对象?

JVisualVM 视觉 GC:

JVisualVM 采样器:

您正在创建 10.000 个 Runnable 实例,但由于您的线程池,一次只能执行 3 个线程。它创建了一个大型的 Runnable 实例队列,这些实例还不能被执行。

尝试增加线程池大小,或减少必须执行的线程数。

注意:大部分垃圾是由 VisualVM 本身监控您的 JVM 创建的。它使用 Java 序列化,效率很低。减少产生的垃圾量的最佳方法是减少轮询间隔。 (或者使用不这样做的配置文件,如 JMC 或 YourKit)

创建任务使用内存,每个添加到工作队列的节点都使用内存。一种更短且更有效的方法是 IntStream

public class Main {
    static void doWork(int task) {
        try {
            System.out.println("starting " + task);
            // business logic
            Thread.sleep(10000);
            System.out.println("... finished " + task);
        } catch (InterruptedException e) {
            throw new AssertionError(e);
        }
    }

    public static void main(String[] args) {
        IntStream.range(0, 100_000)
                .parallel()
                .forEach(Main::doWork);
    }
}

通过将任务分解为可用数量的处理器(它只为您拥有的每个处理器创建两个实际任务),无论任务数量如何,它都使用相同数量的内存