JVM - 参数配置影响线程数
一道面试
找工作是个脑力活也是个体力活啊,最近秋招给我累得够呛,昨天面了杭州大华,被一道 JVM 参数的问题难住了。
引言
面试官:说个常用的 JVM 参数
我:好像有个什么 -Xms,不常用大太熟悉这些参数(语塞中…,本来背了几个,一紧张还是忘了)
面试官:如果 -Xmx 调大,线程数怎么变化
我:变小
面试官:为什么
我:猜的。。。
虽然最后挨到 HR 面,但还是要把面试题捋一捋的。
一、题目相关参数
1.1 三个重要参数
- -Xms 为 jvm 启动时分配 Heap初始内存(最小内存),比如
-Xms200m
,表示分配200M - -Xmx 为 jvm 运行过程中分配 Heap最大内存,比如
-Xms500m
,表示jvm进程最多只能够占用500M内存 - -Xss 为 jvm 启动的每个线程分配的内存大小(stack size),默认JDK1.4中是256K,JDK1.5+中是1M
通常,-Xms
和 -Xmx
设置成一样的,避免每次垃圾回收完成后JVM重新分配内存。因为当Heap不够用时,发生内存抖动,影响程序运行稳定性。
1.2 线程数的变动
准确来说,是 jvm 可生产线程数的数量,由三个方面影响:
- jvm 的堆内存大小
- Thread 的 Stack内存大小
- 系统最大可创建的线程数量
增大堆内存(**-Xms,-Xmx)会减少可创建的线程数量;增大线程栈内存(-Xss**,32 位系统中此参数值最小为 60 K)也会减少可创建的线程数量。因此题中, -Xmx
加大,Heap内存增大,jvm 空闲的内存数(java虚拟机栈等)就更少,那么可以创建的线程也就更少。
系统从以下几个方面影响最大线程数:
- /proc/sys/kernel/pid_max,
- /proc/sys/kernel/thread-max,
- max_user_process(ulimit -u),
- /proc/sys/vm/max_map_count
具体影响过程参考 https://blog.csdn.net/moonpure/article/details/80701878
1.3 三个内存方法
java.lang.Runtime 类中的 freeMemory(), totalMemory(), maxMemory() 这几个方法反映的都是 java 这个进程的内存情况,跟操作系统的内存根本没有关系。
maxMemory()
返回的是java虚拟机(这个进程)能构从操作系统那里挖到的最大的内存,以字节为单位,如果在运行 java 程序的时候,没有添加 -Xmx 参数,那么就是 64 M,也就是说 maxMemory() 返回的大约是6410241024 字节,这是 java 虚拟机默认情况下能从操作系统那里挖到的最大的内存。如果添加了 -Xmx 参数,将以这个参数后面的值为准,例如java -cp you_classpath -Xmx512m your_class
,那么最大内存就是 51210241024 字节。
totalMemory()
返回的是 java 虚拟机现在已经从操作系统那里挖过来的内存大小,也就是java虚拟机这个进程当时所占用的所有内存。如果在运行 java 的时候没有添加 -Xms 参数,那么,在 java 程序运行的过程的,内存总是慢慢的从操作系统那里挖的,基本上是用多少挖多少,直到挖到 maxMemory() 为止,所以totalMemory() 是慢慢增大的。如果用了 -Xms 参数,程序在启动的时候就会无条件的从操作系统中挖 -Xms后面定义的内存数,然后在这些内存用的差不多的时候,再去挖。
freeMemory()
刚才讲到如果在运行java的时候没有添加 -Xms 参数,那么,在 java 程序运行的过程的,内存总是慢慢的从操作系统那里挖的,基本上是用多少挖多少,但是 java 虚拟机100%的情况下是会稍微多挖一点的,这些挖过来而又没有用上的内存,实际上就是 freeMemory(),所以freeMemory()的值一般情况下都是很小的,但是如果你在运行 java 程序的时候使用了 -Xms,这个时候因为程序在启动的时候就会无条件的从操作系统中挖 -Xms 后面定义的内存数,这个时候,挖过来的内存可能大部分没用上,所以这个时候 freeMemory() 可能会有些大。
二、常用参数
2.1 堆设置
- -Xms:初始堆大小
- -Xmx:最大堆大小
- -XX:NewSize=n:设置年轻代大小
- -XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年代年老代和的1/4
- -XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5
- -XX:MaxPermSize=n:设置持久代大小
JVM 中最大堆大小有三方面限制:相关操作系统的数据模型(32-bt还是64-bit)限制;系统的可用虚拟内存限制;系统的可用物理内存限制。32位系统下,一般限制在1.5G~2G;64为操作系统对内存无限制。我在Windows Server 2003 系统,3.5G物理内存,JDK5.0下测试,最大可设置为1478m。
1 |
|
依据的原则是根据Java Performance里面的推荐公式来进行设置。
具体来讲:
- Java整个堆大小设置,Xmx 和 Xms设置为老年代存活对象的3-4倍,即FullGC之后的老年代内存占用的3-4倍
- 永久代 PermSize和MaxPermSize设置为老年代存活对象的1.2-1.5倍。
- 年轻代Xmn的设置为老年代存活对象的1-1.5倍。
- 老年代的内存大小设置为老年代存活对象的2-3倍。
BTW:
1、Sun官方建议年轻代的大小为整个堆的3/8左右, 所以按照上述设置的方式,基本符合Sun的建议。
2、堆大小=年轻代大小+年老代大小, 即 xmx=xmn+老年代大小 。 Permsize不影响堆大小。
3、为什么要按照上面的来进行设置呢? 没有具体的说明,但应该是根据多种调优之后得出的一个结论。
具体调整策略参考 https://blog.csdn.net/losetowin/article/details/78569001
2.2 收集器设置
- -XX:+UseSerialGC :设置串行收集器
- -XX:+UseParallelGC :设置并行收集器
- -XX:+UseParalledlOldGC :设置并行年老代收集器
- -XX:+UseConcMarkSweepGC :设置并发收集器
JVM给了三种选择:串行收集器、并行收集器、并发收集器 ,但是串行收集器只适用于小数据量的情况,所以这里的选择主要针对并行收集器和并发收集器。默认情况下,JDK5.0以前都是使用串行收集器,如果想使用其他收集器需要在启动时加入相应参数。JDK5.0 以后,JVM会根据当前系统配置
吞吐量优先 的并行收集器
如上文所述,并行收集器主要以到达一定的吞吐量为目标,适用于科学技术和后台处理等。
典型配置 :
1 |
|
响应时间优先 的并发收集器
如上文所述,并发收集器主要是保证系统的响应时间,减少垃圾收集时的停顿时间。适用于应用服务器、电信领域等。
典型配置 :
1 |
|
2.3 垃圾回收统计信息
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:filename
2.4 并行收集器设置
-XX:ParallelGCThreads=n :设置并行收集器收集时使用的CPU数。并行收集线程数。
-XX:MaxGCPauseMillis=n :设置并行收集最大暂停时间
-XX:GCTimeRatio=n :设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)
2.5 并发收集器设置
-XX:+CMSIncrementalMode :设置为增量模式。适用于单CPU情况。
-XX:ParallelGCThreads=n :设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。
参考
https://www.cnblogs.com/ceshi2016/p/8447989.html
https://blog.csdn.net/moonpure/article/details/80701878
(完)