Mac M2 Air上Java线程池最大线程数限制问题排查
我正在为异步线程缓存功能做POC验证,目标是在Mac M2 Air上将线程均匀分配到多个核心。但无论分配多少JVM内存,创建线程的数量都存在上限,想了解这是Mac OS的问题还是我遗漏了什么?
参考程序:
/** This is a simple Java program to demonstrate thread pooling with fixed number of threads using the Executor framework. The program creates a fixed thread pool with a specified number of threads, assigns each thread to a processor, and prints the number of threads running on each processor. */ import java.util.HashMap; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Main { public static void main(String[] args) { // Define number of threads int numThreads = 2000; // Create a fixed thread pool with specified number of threads ExecutorService executor = Executors.newFixedThreadPool(numThreads); // Set the number of processors to be used by the JVM int numProcessors = 4; // Arbitrary value System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", Integer.toString(numProcessors)); // Create a HashMap to count threads assigned to each processor Map<Integer, Integer> processorThreadCount = new HashMap<>(); for (int i = 0; i < numProcessors; i++) { processorThreadCount.put(i, 0); } // Assign each thread to a processor and execute it for (int i = 0; i < numThreads; i++) { int processor = i % numProcessors; processorThreadCount.compute(processor, (k, v) -> v + 1); executor.execute(new MyThread(i)); } // Shutdown the thread pool executor.shutdown(); // Print the number of threads running on each processor System.out.println("Threads per processor:"); processorThreadCount.forEach((k, v) -> System.out.println("Processor " + k + ": " + v)); } /** This is a simple thread class that prints the thread ID and the processor it is running on. */ private static class MyThread implements Runnable { private int id; public MyThread(int id) { this.id = id; } public void run() { System.out.println("Thread " + id + " running on processor " + Thread.currentThread().getName()); } } }
当将线程数修改为9000时:
int numThreads = 9000;
出现错误:
[0.167s][warning][os,thread] Failed to start thread "Unknown thread" - pthread_create failed (EAGAIN) for attributes: stacksize: 102400k, guardsize: 16k, detached. [0.167s] [warning][os,thread] Failed to start the native thread for java.lang.Thread "pool-1-thread-2022"Exception in thread "main" java.lang.OutOfMemoryError: unable to create native thread: possibly out of memory or process/resource limits reachedat java.base/java.lang.Thread.start0(Native Method) at java.base/java.lang.Thread.start(Thread.java:803) at java.base/java.util.concurrent.ThreadPoolExecutor.addWorker(ThreadPoolExecutor.java:937) at java.base/java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1343)at Main.main(Main.java:151) ^C%
我尝试通过java -Xss10m Main将JVM栈大小从初始的1Mb调整到10Mb,甚至增加到1GB,但每次都会出现pool-1-thread-2022线程分配失败的OutOfMemoryError错误。想知道是什么导致了这个限制?
当线程数设为2000时,程序输出如下:
Threads per processor: Processor 0: 500 Processor 1: 500 Processor 2: 500 Processor 3: 500
1. MacOS的进程线程数系统限制
MacOS对单个进程能创建的原生线程数量有硬性限制,这个限制和JVM堆内存无关。你可以通过以下命令查看当前限制:
- 查看用户级线程数上限:
ulimit -u - 查看系统级线程数上限:
sysctl kern.num_taskthreads
当进程创建的线程数接近这个上限时,系统会拒绝创建新线程,抛出pthread_create failed (EAGAIN)错误,也就是你看到的异常。
2. 线程栈内存的反向影响
每个Java线程对应一个系统原生线程,每个线程都有独立的栈空间。你调整-Xss增大栈大小,会让每个线程占用更多虚拟内存,反而更快耗尽进程的内存配额,导致无法创建更多线程。正确的做法是调小栈大小(比如-Xss256k),这样每个线程占用的内存更少,能创建的线程数会更多。
3. 线程池设计的误区
Executors.newFixedThreadPool(numThreads)会一次性创建所有指定数量的线程,这在线程数很大时是非常低效的——M2 Air是8核(4性能核+4能效核),几千个线程会导致频繁的上下文切换,反而严重降低性能。
实际场景中,线程池大小应该根据任务类型设置:
- CPU密集型任务:线程数≈CPU核心数
- IO密集型任务:线程数≈CPU核心数×(1+IO耗时/CPU耗时)
你想让线程均匀分配到核心,完全不需要创建几千个线程,少量线程就能占满所有核心的计算资源。
4. 代码中处理器分配逻辑无效
你用i % numProcessors只是做了计数统计,并没有真正将线程绑定到指定处理器。线程的调度完全由操作系统负责,ForkJoinPool.common.parallelism的设置只对ForkJoinPool生效,对FixedThreadPool没有作用。
如果确实需要将线程绑定到特定核心,Java标准库没有直接支持,需要借助JNI或非标准API(比如OpenJDK的jdk.internal.misc.Unsafe),但这种操作不推荐在生产环境使用,因为会破坏JVM的跨平台性,且对性能提升有限。
内容的提问来源于stack exchange,提问作者ipmob

