【转载】JVM能够开启多少线程

JVM能够开启多少线程


   最近在看<<Java并发编程实战>>一书过程中,在Task Execution一节中讲到,针对每个任务都启动一个线程来处理,尤其在大量创建线程的场景下,会给工程实践带来很多问题.


   1)线程的创建和销毁都是有开销的。线程的创建需要时间,如果针对每个任务都启动线程处理,则无疑会造成请求不能得到尽快处理。如果请求比较频繁而且轻量级,为每个请求创建新线程会消耗大量资源。

   2)资源消耗.线程会消耗系统资源,尤其是内存。如果可运行的线程(Runnable状态)数量大于可用处理器数量,那么有些线程会得不到调度。这些线程一旦数量太多就会占用大量内存,给GC带来回收压力。同时大量的线程对CPU的竞争带来的上下文切换也会影响系统性能。如果已经有足够的线程来让CPU保持忙碌,那么再创建新的线程非但不能提高CPU利用率,反而会降低性能。

   3)稳定性。能够创建的线程数量是有上限的。这个数量与平台相关,也受限于多个因素:JVM启动参数(-Xss),创建Thread时在构造函数中指定的线程栈大小(stack size)和底层操作系统对线程数的限制。


   从第三点可见,线程数量和线程栈大小(通过-Xss或stack size指定,一般为0.5M-1M)、可用内存和操作系统限制有关系。因此本文讨论一下,究竟JVM最多能够开启多少线程。


   不考虑OS限制的情况下,RAM越大,线程栈越小,可以创建的线程就越多。一个理论上的计算公式如下:

   Max number of threads(最大线程数)=(进程最大可用内存-JVM分配内存-OS Reserved Memory(JNI,本地方法栈))/thread stack size(线程栈大小)

   对于32位操作系统,允许创建的线程数量一个重要的制约因素便是内存RAM。由于寻址空间仅仅有2^32byte(4GB),按照线程栈大小为0.5M来计算,最大线程数为2^13,即不到1万。如果按照线程栈大小为1M来计算,最大线程数不过数千。

   对于64为操作系统,内存不再是最大制约因素,最大线程数受限于Linux内核.以我的Ubuntu 64-bit 操作系统,16GB RAM为例,执行如下代码:

  


 
 
  1. package snow.rabbit.java.thread;
  2. /**
  3. *
  4. * @author lawrence
  5. *
  6. */
  7. public class TestMaxSupportThreads {
  8. private static final class TestThread extends Thread
  9. {
  10. private final int number;
  11. public TestThread(int number) {
  12. this.number = number;
  13. }
  14. public TestThread(int number,int stackSize) {
  15. super( null, null, "Thread-Hi-" +number,stackSize);
  16. this.number = number;
  17. }
  18. @Override
  19. public void run() {
  20. if (number% 100 == 0)
  21. {
  22. System.out.println( "Thread "+number+ " started.");
  23. }
  24. try {
  25. Thread.sleep(Long.MAX_VALUE);
  26. } catch (InterruptedException e) {
  27. e.printStackTrace();
  28. }
  29. }
  30. }
  31. /**
  32. * @param args
  33. */
  34. public static void main(String[] args) {
  35. int i= 0;
  36. try {
  37. for (i= 0; i < 200; i++)
  38. {
  39. Thread t= new TestThread(i, 2<< 19); //512K
  40. t.start();
  41. }
  42. } catch (Throwable e) {
  43. System.out.println( "Error occured when creating thread "+i);
  44. e.printStackTrace();
  45. }
  46. }
  47. }

   输出结果如下:

   Thread 32100 started.
Error occured when creating thread 32129
java.lang.OutOfMemoryError: unable to create new native thread
        at java.lang.Thread.start0(Native Method)
        at java.lang.Thread.start(Thread.java:640)
        at TestMaxSupportThreads.main(TestMaxSupportThreads.java:37)

   此时内存还有很大剩余,但是线程创建到32129时便出现异常,这是由于OS的限制导致,和kernal/ulimit有关。具体见如下设置:

   1)/proc/sys/kernel/pid_max :系统允许的最大pid

     与用户态不同,对于Linux内核而言,进程和线程之间的区别并不大,线程也不过是共享内存空间的进程。每个线程都是一个轻量级进程(Light Weight Process),都有自己的唯一PID(或许叫TID更合适一些)和一个TGID(Thread group ID),TGID是启动整个进程的thread的PID.

     简单来说,当一个进程被创建的时候,它其实是一个PID和TGID数值相同线程。当线程A启动线程B时,线程B会有自己的唯一PID,但它的TGID会从A继承而来。这样通过PID线程可以独立得到调度,而相同的TGID可以知道哪些线程属于同一个进程,这样可以共享资源(RAM,虚拟内存、文件等)。

通过ps -fL可以看到LWP轻量级进程信息:
lizhong@lizhongpc:~$ ps -fL
UID        PID  PPID   LWP  C NLWP STIME TTY          TIME CMD
lizhong   4167  2252  4167  0    1 14:49 pts/2    00:00:00 -bash
lizhong   6592  4167  6592  0    1 16:40 pts/2    00:00:00 ps -fL

我们启动200个线程后,可以看到LWP显著增加。
lizhong@lizhongpc:~$ ps -fL
UID PID PPID LWP C NLWP STIME TTY TIME CMD
lizhong 4167 2252 4167 0 1 14:49 pts/2 00:00:00 -bash
lizhong 6828 4167 6828 0 1 16:40 pts/2 00:00:00 ps -fL

   2)/proc/sys/kernel/threads-max :系统支持的最大线程数

   3)max_user_process(ulimit -u):每个用户允许的最大进程数

   4)/proc/sys/vm/max_map_count: Linux支持虚拟内存,也就是交换空间,可以把磁盘的一部分作为RAM的扩展,逻辑存储和物理存储的映射就要保存在地址映射表中。max_map_count限制了线程可以拥有的VMAs (Virtual Memory Areas)。

 

   将pid_max,threads-max,max_user_process和max_map_count都改为4194000后,分别将Thread构造函数中stackSize指定为1M,512K,64K,达到的最大线程数分别是423100,626400,626400. 

   

   从中我们可以看出两点:

   1)进程可用内存不仅仅局限于RAM,还包括Swap分区(虚拟内存),在线程不断创建的过程中,首先空闲的RAM不断减少,然后是空闲Swap不断减少。

   2)我们在线程构造函数中指定了stackSize分别为512K和64K,但达到的最大线程数是相差无几的,说明JVM并非绝对按照我们指定的大小为线程分配内存。

   

   综上所述,JVM最大开启线程数取决于可用内存(包括虚拟内存)和stack size, 在OS层面又和pid_max、threads-max、max_user_process和max_map_count相关。

   下面是我在16G内存下,stack size 设置为512K,各项OS内核参数调整到最大,创建线程的过程中不断跟踪内存情况和轻量级线程情况的截图:

yangy@yangy:~/lizhongspace$ free -m
             total       used       free     shared    buffers     cached
Mem:         15935        579      15355         41         50        245
-/+ buffers/cache:        282      15652
Swap:        19070          0      19070
yangy@yangy:~/lizhongspace$ free -m
             total       used       free     shared    buffers     cached
Mem:         15935       4805      11130         41         50        245
-/+ buffers/cache:       4508      11426
Swap:        19070          0      19070
yangy@yangy:~/lizhongspace$ free -m
             total       used       free     shared    buffers     cached
Mem:         15935       7013       8921         41         50        245
-/+ buffers/cache:       6717       9217
Swap:        19070          0      19070
yangy@yangy:~/lizhongspace$ free -m
             total       used       free     shared    buffers     cached
Mem:         15935       8394       7540         41         50        245
-/+ buffers/cache:       8098       7836
Swap:        19070          0      19070
yangy@yangy:~/lizhongspace$ free -m
             total       used       free     shared    buffers     cached
Mem:         15935      11192       4742         41         50        245
-/+ buffers/cache:      10895       5039
Swap:        19070          0      19070
yangy@yangy:~/lizhongspace$ free -m
             total       used       free     shared    buffers     cached
Mem:         15935      12630       3304         41         50        245
-/+ buffers/cache:      12334       3600
Swap:        19070          0      19070
yangy@yangy:~/lizhongspace$ free -m
             total       used       free     shared    buffers     cached
Mem:         15935      14248       1686         41         50        245
-/+ buffers/cache:      13951       1983
Swap:        19070          0      19070
yangy@yangy:~/lizhongspace$ free -m
             total       used       free     shared    buffers     cached
Mem:         15935      15091        843         41         50        245
-/+ buffers/cache:      14794       1140
Swap:        19070          0      19070
yangy@yangy:~/lizhongspace$ free -m
             total       used       free     shared    buffers     cached
Mem:         15935      15758        176         27          0         32
-/+ buffers/cache:      15725        209
Swap:        19070        635      18435
yangy@yangy:~/lizhongspace$ free -m
             total       used       free     shared    buffers     cached
Mem:         15935      15787        147          2          0         13
-/+ buffers/cache:      15773        161
Swap:        19070       8706      10364
yangy@yangy:~/lizhongspace$ free -m
             total       used       free     shared    buffers     cached
Mem:         15935      15788        146          7          0         26
-/+ buffers/cache:      15761        173
Swap:        19070      10723       8347
yangy@yangy:~/lizhongspace$ ps -fL
UID          PID    PPID     LWP  C NLWP STIME TTY          TIME CMD
yangy       2237    2236    2237  0    1 21:20 pts/11   00:00:00 -bash
yangy     631902    2237  631902  0    1 21:31 pts/11   00:00:00 ps -fL

以上内容同时发表在我的个人博客清欢de个人博客中,欢迎大家访问。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值