为啥运行单线程 Java 程序会导致许多内核处于活动状态?

Posted

技术标签:

【中文标题】为啥运行单线程 Java 程序会导致许多内核处于活动状态?【英文标题】:Why does running a single threaded Java program result in many cores active?为什么运行单线程 Java 程序会导致许多内核处于活动状态? 【发布时间】:2021-10-05 18:26:33 【问题描述】:

我正在对从Object 转换为数据类型的总延迟进行基准测试。但是我遇到了一个非常奇怪的 Java 集合行为,在本例中是 List

List<Long> data = new ArrayList<>();
int SIZE = 50_000_000;

long currentTime = System.currentTimeMillis();
for (int i = 0; i < SIZE; i++) 
    data.add(currentTime++);

在我的 Intel i5 8250u(4 核)上运行上述代码时,CPU 利用率在 IntelliJ Idea 上运行时为 100%。所以我认为这可能是因为 IntelliJ,因此我将代码移动到具有 20 个内核的 Azure VM(运行 CentOS 7.4),令我惊讶的是,这段代码最终消耗了 1500% CPU(结果来自top 命令)是 15 核。

我无法理解的是:单线程 Java 程序代码如何消耗超过 1 个内核?

编辑:

复制步骤:

运行上面的代码。

机器配置:

笔记本电脑:4 核 16Gb RAM,Oracle Java 1.8_161

Azure 虚拟机:20 核 148GB RAM,Oracle Java 1.8_161

笔记本电脑上 JVisualVM 的输出:

【问题讨论】:

因为 JVM 本身在实际 Java 代码之外还有一些工作要做(包括垃圾收集和 JIT 编译热循环),但其中一些开销可以并行化。当 ArrayList 必须重新分配时,甚至可能并行化 memcpy。 但是考虑到代码的简单性,1500% 已经很多了。 是的,15 核是我所期望的更多线程级并行性,对 JVM 进行分析并查看它在哪里花费了所有 CPU 时间可能会很有趣。 (例如perf record / perf report)。 JVM 本身是提前编译的,并且对于分析工具来说处理起来相对简单,除了它的 JIT 编译的客户代码块。 我想也许你得到了反对票,因为它提出了一个复杂的问题,但它没有足够的细节来真正调查这个问题。您必须描述更多关于如何完成测量、测量记录以及可用于重现它们的命令。 你在 CentOS 系统上使用的是什么 JVM?它与您的桌面不同吗? 【参考方案1】:

你的测试除了分配内存什么都不做。因此它很快耗尽了初始堆内存,导致 Full GC 运行。然后堆增加,但又很快被填满,导致另一个 Full GC,等等。

$ java -XX:+PrintGC Test
[GC (Allocation Failure)  27648K->20757K(104448K), 0.0296779 secs]
[GC (Allocation Failure)  48405K->40538K(132096K), 0.0293287 secs]
[GC (Allocation Failure)  83084K->82453K(138752K), 0.0615143 secs]
[Full GC (Ergonomics)  82453K->75113K(225792K), 0.5392036 secs]
[GC (Allocation Failure)  124981K->139346K(254464K), 0.0563272 secs]
[Full GC (Ergonomics)  139346K->112504K(353792K), 0.5240216 secs]
[GC (Allocation Failure)  185709K->208841K(380416K), 0.0864858 secs]
[Full GC (Ergonomics)  208841K->168513K(512512K), 0.9035611 secs]
...

因此,您观察到的是一系列较长的 Full GC 周期。 JDK 8 中默认的垃圾收集器是 Parallel,并行 GC 线程的数量等于 CPU 的数量。

如果您在线程模式 (-t) 下运行 async-profiler,您会发现几乎所有 CPU 时间都花在了多线程中运行垃圾回收上。

【讨论】:

哇。我很惊讶。太感谢了。我尝试提供更大的初始堆内存,但没有看到这种行为。

以上是关于为啥运行单线程 Java 程序会导致许多内核处于活动状态?的主要内容,如果未能解决你的问题,请参考以下文章

Java:两个 WAITING + 一个 BLOCKED 线程, notify() 导致活锁, notifyAll() 没有,为啥?

为啥使用更多线程会导致运行时间变慢?

为啥在IOS上运行torchscript模型推理会导致线程错误?

java-并发编程

什么会导致Java应用程序的CPU使用率飙升

Java中是否父线程阻塞后子线程就无法继续执行?