我正在Linux机箱(AMD 6 Core,16 GB RAM)上使用JVM(Oracle 1.7 64位),以了解应用程序中的线程数如何影响性能。我希望测量上下文切换会降低性能。
我创建了一个创建线程执行池的小应用程序:
Executors.newFixedThreadPool(numThreads)
每次运行程序时,我都会调整numThreads
,以查看它的效果。
然后我将numThread
个作业(java.util.concurrent.Callable
的实例)提交到池中。每一个都递增一个AtomicInteger
,做一些工作(创建一个随机整数数组并将其洗牌),然后睡一会儿。我们的想法是模拟Web服务调用。最后,作业重新提交给池,以便我总是有numThreads
个工作。
我正在测量吞吐量,就像每分钟处理的作业数量一样。
有几千个线程,我每分钟可处理多达400,000个作业。超过8000个线程,结果开始变化很大,这表明上下文切换正成为一个问题。但是我可以继续将线程数增加到30,000,并且仍然可以获得更高的吞吐量(每分钟420,000到570,000个作业)。
现在的问题是:我得到的java.lang.OutOfMemoryError: Unable to create new native thread
工作量超过31,000个。我试过设置-Xmx6000M
这没有帮助。我试过玩-Xss
,但这也无济于事。
我读过ulimit
可能有用,但ulimit -u 64000
增加并没有改变任何内容。
有关信息:
[root@apollo ant]# ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 127557
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 1024
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
问题#1:我需要做些什么才能创建更大的线程池?
问题2:我应该在什么阶段看到上下文切换真的会降低吞吐量并导致流程停止?
以下是一些结果,在我修改它以进行更多处理(如建议的那样)并开始记录平均响应时间(如同建议的那样)。
// ( (n_cores x t_request) / (t_request - t_wait) ) + 1
// 300 ms wait, 10ms work, roughly 310ms per job => ideal response time, 310ms
// ideal num threads = 1860 / 10 + 1 = 187 threads
//
// results:
//
// 100 => 19,000 thruput, 312ms response, cpu < 50%
// 150 => 28,500 thruput, 314ms response, cpu 50%
// 180 => 34,000 thruput, 318ms response, cpu 60%
// 190 => 35,800 thruput, 317ms response, cpu 65%
// 200 => 37,800 thruput, 319ms response, cpu 70%
// 230 => 42,900 thruput, 321ms response, cpu 80%
// 270 => 50,000 thruput, 324ms response, cpu 80%
// 350 => 64,000 thruput, 329ms response, cpu 90%
// 400 => 72,000 thruput, 335ms response, cpu >90%
// 500 => 87,500 thruput, 343ms response, cpu >95%
// 700 => 100,000 thruput, 430ms response, cpu >99%
// 1000 => 100,000 thruput, 600ms response, cpu >99%
// 2000 => 105,000 thruput, 1100ms response, cpu >99%
// 5000 => 131,000 thruput, 1600ms response, cpu >99%
// 10000 => 131,000 thruput, 2700ms response, cpu >99%, 16GB Virtual size
// 20000 => 140,000 thruput, 4000ms response, cpu >99%, 27GB Virtual size
// 30000 => 133,000 thruput, 2800ms response, cpu >99%, 37GB Virtual size
// 40000 => - thruput, -ms response, cpu >99%, >39GB Virtual size => java.lang.OutOfMemoryError: unable to create new native thread
我将它们解释为:
1)尽管应用程序在96.7%的时间内处于休眠状态,但仍然需要进行大量的线程切换 2)上下文切换是可测量的,并在响应时间中显示。
有趣的是,在调整应用程序时,您可能会选择可接受的响应时间,比如说400毫秒,并增加线程数,直到您获得响应时间,在这种情况下会让应用程序处理95一千分钟请求。
通常人们会说理想的线程数接近核心数。在具有等待时间的应用程序中(阻塞的线程,例如等待数据库或Web服务响应),计算需要考虑(参见上面的等式)。但是,当您查看结果或调整到特定的响应时间时,即使理论上的理想也不是真正的理想。
答案 0 :(得分:7)
我得到了一个java.lang.OutOfMemoryError:无法创建超过31,000个作业的新本机线程。我试过设置-Xmx6000M没有帮助。我试过玩-Xss,但这也无济于事。
-Xmx设置无效,因为未从堆中分配线程堆栈。
正在发生的事情是,JVM要求操作系统提供一个内存段(堆外!)来保存堆栈,操作系统拒绝请求。最可能的原因是ulimit或OS内存资源问题:
“数据段大小”ulimit是无限的,因此不应该是问题。
这样就留下了内存资源。每次1Mb的30,000个线程约为30Gb,这比你拥有的物理内存要多得多。我的猜测是30Gb的虚拟内存有足够的交换空间,但是你已经把边界推得太远了。
-Xss设置应该有帮助,但是您需要使请求的堆栈大小小于默认大小1m
。此外,最小尺寸很小。
问题#1:我需要做些什么才能创建更大的线程池?
将默认堆栈大小减小到当前值以下,或者增加可用虚拟内存量。 (不推荐使用后者,因为看起来你已经严重过度分配了。)
问题2:我应该在什么阶段看到上下文切换真的会降低吞吐量并导致流程停止?
无法预测。它将高度依赖于线程实际执行的操作。事实上,我认为你的基准测试不会给你答案,告诉你一个真正的多线程应用程序将如何表现。
Oracle网站对线程堆栈空间的主题说this:
在Java SE 6中,Sparc上的默认值在32位VM中为512k,在64位VM中为1024k。在x86 Solaris / Linux上,32位VM为320k,64位VM为1024k。
在Windows上,从二进制文件(java.exe)中读取默认的线程堆栈大小。从Java SE 6开始,这个值在32位VM中为320k,在64位VM中为1024k。
您可以使用-Xss选项运行来减少堆栈大小。例如:
java -server -Xss64k
请注意,在某些版本的Windows上,操作系统可能会使用非常粗略的粒度来舍入线程堆栈大小。如果请求的大小小于默认大小1K或更多,则堆栈大小向上舍入为默认值;否则,堆栈大小向上舍入为1 MB的倍数。
64k是每个线程允许的最小堆栈空间。
答案 1 :(得分:2)
这里有一些观点/方法,我会遵循:
查看上下文切换中使用的数据。而不是布尔或字符串尝试使用一些大的列表或地图。
尝试使用缓存池,而不是尝试在启动时创建固定池。
不要让线程在完成一些小工作后消失,让它们活着,然后一次又一次地做一小块工作。
尽量保持线程处理时间更长。