我可以设置 Java VM 可用的线程/CPU 数量吗?
Posted
技术标签:
【中文标题】我可以设置 Java VM 可用的线程/CPU 数量吗?【英文标题】:Can I set the number of Threads/CPUs available to the Java VM? 【发布时间】:2016-02-16 20:13:30 【问题描述】:我想限制 Java VM 可用的线程/进程数,类似于设置可用内存的方式。我希望能够将其指定为仅使用 1 个线程或任意数量。
注意:我无法在代码中设置它,因为我想限制的代码是一个我无法修改源代码的库。因此,它必须是对虚拟机级别施加的硬性上限。 (或者,如果您可以对可以覆盖库的应用程序本身施加线程限制?)
注意 2:这样做的目的是进行性能测试,以限制我要测试的库,看看当它可以访问不同数量的 CPU/线程时它的性能如何。
谢谢!
【问题讨论】:
你为什么要这样做? @Matt Timmermans 一些 JVM 在无法再创建一个线程时的行为是抛出OutOfMemoryError'
。
您可以在一个 CPU 上拥有任意数量的线程。几乎所有时候,如果一个库试图创建一个线程并且不能,那么它将无法工作。如果您有一个创建一大堆线程的库,那么它可能有一个配置选项来设置它将使用的最大数量。
为什么要限制Java线程的数量?如果您想要进行与实际场景有任何相似之处的性能测试,您应该限制 Java VM 可用的内核数量(可能还有亲和力)。
这是操作系统的任务,而不是 JVM。如果你使用linux,可以使用专用的cgroup。
【参考方案1】:
JVM 中的 CPU 限制问题在 Java 10 中得到解决,并从 build 8u191 向后移植到 Java 8:
-XX:ActiveProcessorCount=2
【讨论】:
适用于默认线程池中的线程数。但是不能限制 CPU。【参考方案2】:如果您使用的是 linux,只需将 java
启动器包装在 numactl
/ taskset
命令中。这允许 JVM 生成任意数量的线程,但将它们安排在固定数量的物理内核上。
其他操作系统也可以使用类似的工具。
【讨论】:
不幸的是,HotSpot JVM 在 Linux 上不尊重taskset
(错误JDK-6515172)。 JVM 仍然会认为所有处理器都可用,从而导致过多的 GC 线程、编译器线程以及应用程序和库可能依赖的 Runtime.availableProcessors()
的值,例如默认 ForkJoinPool 的大小也是基于availableProcessors
。
@apangin,编译器/GC线程可以调整,其余的似乎也不是不可逾越的障碍。可能仍然足以进行测试。【参考方案3】:
尝试以 Windows 用户的“亲和力”运行您的程序。
例如:而不是运行“java Test”,您应该运行: 当您需要 1 个核心时,“启动 /affinity 1 java Test”; 当您需要 2 个核心时,“启动 /affinity 3 java Test”; …… 使用的参数应遵循以下格式:
https://blogs.msdn.microsoft.com/santhoshonline/2011/11/24/how-to-launch-a-process-with-cpu-affinity-set/
您可以使用“System.out.println(Runtime.getRuntime().availableProcessors()); 来检查。
【讨论】:
【参考方案4】:在 JDK 8u191 之前,没有 VM 标志或属性来控制 Java 可用的 CPU 数量,即 Runtime.availableProcessors()
返回的值。
在 Windows 和 Solaris 上,如果您设置了进程关联掩码,它也会影响Runtime.availableProcessors()
。这在 Linux 上不起作用,请参阅 JDK-6515172。
还有一个使用 LD_PRELOAD 补丁或操作系统级别技巧的 Linux 解决方法,请参阅this question 中的详细信息。
更新
自 JDK 8u121 起,JVM 现在尊重 Linux 上的任务集,请参阅 JDK-6515172 从 JDK 8u191 开始,有一个 JVM 标志-XX:ActiveProcessorCount=nn
来覆盖 JVM 可见的 CPU 数量,请参阅 JDK-8146115
【讨论】:
这个答案不再有效,因为-XX:ActiveProcessorCount
标志也被反向移植到 OpenJDK 8。【参考方案5】:
我建议您可以实现并安装自己的 SecurityManager,它会跟踪创建的线程数并在达到最大值时抛出错误。
根据对this question 的接受回答,每次创建/启动新线程时都会检查带有“modifyThreadGroup”目标的RuntimePermission
。
更新
SecurityManager 的第一种方法可能是这样的:
class MySecurityManager extends SecurityManager
private final int maxThreads;
private int createdThreads;
public MySecurityManager(int maxThreads)
super();
this.maxThreads=maxThreads;
@Override
public void checkAccess(Thread t)
// Invoked at Thread *instantiation* (not at the start invokation).
super.checkAccess(t);
// Synchronized to prevent race conditions (thanks to Ibrahim Arief) between read an write operations of variable createdThreads:
synchronized(this)
if (this.createdThreads == this.maxThreads)
throw new Error("Maximum of threads exhausted");
else
this.createdThreads++;
当然,必须进行进一步的测试以确保始终允许系统线程。并且保持这个算法不会在线程结束时减少计数。
【讨论】:
'alphaloop' 的'accepted' 下面的答案说实际上不可能通过SecurityManager 来做到这一点?无论如何,我不想只监控它,我想指定一个上限,以便将其限制在特定级别,以便我可以看到库在不同条件下的性能。 我明白了。我已经在我的答案中包含并测试了一个限制线程数的 SecurityManager 。你可以从这个开始,然后继续测试。 此代码可能会导致竞争条件扰乱检查和增量。至少,使用this.createdThreads > this.maxThreads
而不是直接等效。充其量,重构代码以使用更好的并发机制。
@Ibrahim Arief 对。我已经更新了我的帖子,尽管新的同步会稍微降低性能。谢谢。
@SimonLogic 事实上,我没有(阅读最后的注释)。为了获得准确、最新的活动线程计数,我想必须包含一个 ReferenceQueue。【参考方案6】:
作为最后的手段,我可以在任务管理器中将 Java VM 的关联设置为仅使用 1 个(或更多)CPU。 (这当然仍然允许 1 个 CPU 上的多个线程,但如果没有其他人有更好的想法,这可能是最接近我想要的东西)
【讨论】:
【参考方案7】:为了测试这一点,我使用了更高的 java 版本(java 版本“14.0.2”2020-07-14)和这个选项:
-XX:ActiveProcessorCount=2
但是,这不会限制 JVM 使用超过 2 个处理器。如果可用,它会使用更多。
根据我的理解(docs)和实验,这会影响线程池中线程数的计算等。但是 NOT 是否对 CPU 数量施加任何限制分配给进程。
限制 cpu 使用的一种方法是在 docker 容器内运行 java 进程并使用
"--cpus"
泊坞窗选项。
这是我特别在 Mac OS 上使用的,目前没有支持的实用程序来限制 CPU。
另一种方法是使用特定于操作系统的实用程序,例如 taskset / isolcpus / cgroups
(在 Linux 上可用)。
所以如果问题是限制java进程使用的cpu,那么ActiveProcessorCount选项本身就是useless的。在 docker 容器中运行它,或者使用操作系统特定的包装器来处理 CPU 或其他资源的分配/限制。
【讨论】:
以上是关于我可以设置 Java VM 可用的线程/CPU 数量吗?的主要内容,如果未能解决你的问题,请参考以下文章