一道面试

找工作是个脑力活也是个体力活啊,最近秋招给我累得够呛,昨天面了杭州大华,被一道 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
2
3
4
5
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k
-Xmx3550m # 设置JVM最大可用内存为3550M。
-Xms3550m # 设置JVM促使内存为3550m。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。
-Xmn2g # 设置年轻代大小为2G。整个堆大小=年轻代大小 + 年老代大小 + 持久代大小 。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
-Xss128k # 设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。更具应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。

依据的原则是根据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
2
3
java -Xmx3800m -Xms3800m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20
-XX:+UseParallelGC # 选择垃圾收集器为并行收集器。 此配置仅对年轻代有效。即上述配置下,年轻代使用并发收集,而年老代仍旧使用串行收集。
-XX:ParallelGCThreads=20 # 配置并行收集器的线程数,即:同时多少个线程一起进行垃圾回收。此值最好配置与处理器数目相等。

响应时间优先 的并发收集器

如上文所述,并发收集器主要是保证系统的响应时间,减少垃圾收集时的停顿时间。适用于应用服务器、电信领域等。
典型配置 :

1
2
3
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
-XX:+UseConcMarkSweepGC # 设置年老代为并发收集。测试中配置这个以后,-XX:NewRatio=4的配置失效了,原因不明。所以,此时年轻代大小最好用-Xmn设置。
-XX:+UseParNewGC # 设置年轻代为并行收集。可与CMS收集同时使用。JDK5.0以上,JVM会根据系统配置自行设置,所以无需再设置此值。

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

(完)