JVM能够开启多少线程
2015-11-11 21:21
357 查看
最近在看<<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为例,执行如下代码:
输出结果如下:
此时内存还有很大剩余,但是线程创建到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,虚拟内存、文件等)。
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内核参数调整到最大,创建线程的过程中不断跟踪内存情况和轻量级线程情况的截图:
以上内容同时发表在我的个人博客清欢de个人博客中,欢迎大家访问。
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为例,执行如下代码:
package snow.rabbit.java.thread; /** * * @author lawrence * */ public class TestMaxSupportThreads { private static final class TestThread extends Thread { private final int number; public TestThread(int number) { this.number = number; } public TestThread(int number,int stackSize) { super(null, null, "Thread-Hi-" +number,stackSize); this.number = number; } @Override public void run() { if (number%100 ==0) { System.out.println("Thread "+number+" started."); } try { Thread.sleep(Long.MAX_VALUE); } catch (InterruptedException e) { e.printStackTrace(); } } } /** * @param args */ public static void main(String[] args) { int i=0; try { for (i=0; i < 200; i++) { Thread t=new TestThread(i,2<<19);//512K t.start(); } } catch (Throwable e) { System.out.println("Error occured when creating thread "+i); e.printStackTrace(); } } }
输出结果如下:
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个人博客中,欢迎大家访问。
相关文章推荐
- MYeclipse和eclipse常用快捷键
- Excel 链接的图片
- MySQL文件位置管理
- 线性表(二)——单链表(4)——单链表的递增判断
- C++ const限定符总结(C++ primer)
- 数据挖掘十大经典算法
- python 函数学习
- muduo库阅读(34)——Net部分:轮询器基类Poller
- T010 高度判断
- $watch监听数据变化和run方法
- git使用笔记
- VS2013+opencv2.4.11
- 第三方类库学习笔记:CustomShapeImageView 自定义形状的ImageView
- EASYUI 树形菜单 fastjson JAVA嵌套对象转JSON
- 《leetCode》:Next Permutation
- 开源框架之——xUtils
- 项目管理(二)责任划分
- 求一个点到任意两个点所在直线距离的代码公式(增加推理)
- 给一个无括号的布尔表达式求所有组合数
- 开发性能测试工具——自己动手实现迭代功能