JVM 调优之 -Xms -Xmx -Xmn -Xss_java_帅性而为 1 号的博客 - CSDN 博客
原博客地址:http://uule.iteye.com/
一、JVM 内存模型及垃圾收集算法
1. 根据 Java 虚拟机规范,JVM 将内存划分为:
- New(年轻代)
- Tenured(年老代)
- 永久代(Perm)
其中 New 和 Tenured 属于堆内存,堆内存会从 JVM 启动参数(-Xmx:3G)指定的内存中分配,Perm 不属于堆内存,有虚拟机直接分配,但可以通过 -XX:PermSize -XX:MaxPermSize 等参数调整其大小。
- 年轻代(New):年轻代用来存放 JVM 刚分配的 Java 对象
- 年老代(Tenured):年轻代中经过垃圾回收没有回收掉的对象将被 Copy 到年老代
- 永久代(Perm):永久代存放 Class、Method 元信息,其大小跟项目的规模、类、方法的量有关,一般设置为 128M 就足够,设置原则是预留 30% 的空间。
New 又分为几个部分:
- Eden:Eden 用来存放 JVM 刚分配的对象
- Survivor1
- Survivro2:两个 Survivor 空间一样大,当 Eden 中的对象经过垃圾回收没有被回收掉时,会在两个 Survivor 之间来回 Copy,当满足某个条件,比如 Copy 次数,就会被 Copy 到 Tenured。显然,Survivor 只是增加了对象在年轻代中的逗留时间,增加了被垃圾回收的可能性。
2. 垃圾回收算法
垃圾回收算法可以分为三类,都基于标记 - 清除(复制)算法:
- Serial 算法(单线程)
- 并行算法
- 并发算法
JVM 会根据机器的硬件配置对每个内存代选择适合的回收算法,比如,如果机器多于 1 个核,会对年轻代选择并行算法,关于选择细节请参考 JVM 调优文档。
稍微解释下的是,并行算法是用多线程进行垃圾回收,回收期间会暂停程序的执行,而并发算法,也是多线程回收,但期间不停止应用执行。所以,并发算法适用于交互性高的一些程序。经过观察,并发算法会减少年轻代的大小,其实就是使用了一个大的年老代,这反过来跟并行算法相比吞吐量相对较低。
还有一个问题是,垃圾回收动作何时执行?
- 当年轻代内存满时,会引发一次普通 GC,该 GC 仅回收年轻代。需要强调的时,年轻代满是指 Eden 代满,Survivor 满不会引发 GC
- 当年老代满时会引发 Full GC,Full GC 将会同时回收年轻代、年老代
- 当永久代满时也会引发 Full GC,会导致 Class、Method 元信息的卸载
另一个问题是,何时会抛出 OutOfMemoryException,并不是内存被耗空的时候才抛出
- JVM98% 的时间都花费在内存回收
- 每次回收的内存小于 2%
满足这两个条件将触发 OutOfMemoryException,这将会留给系统一个微小的间隙以做一些 Down 之前的操作,比如手动打印 Heap Dump。
二、内存泄漏及解决方法
1. 系统崩溃前的一些现象:
- 每次垃圾回收的时间越来越长,由之前的 10ms 延长到 50ms 左右,FullGC 的时间也有之前的 0.5s 延长到 4、5s
- FullGC 的次数越来越多,最频繁时隔不到 1 分钟就进行一次 FullGC
- 年老代的内存越来越大并且每次 FullGC 后年老代没有内存被释放
之后系统会无法响应新的请求,逐渐到达 OutOfMemoryError 的临界值。
2. 生成堆的 dump 文件
通过 JMX 的 MBean 生成当前的 Heap 信息,大小为一个 3G(整个堆的大小)的 hprof 文件,如果没有启动 JMX 可以通过 Java 的 jmap 命令来生成该文件。
3. 分析 dump 文件
下面要考虑的是如何打开这个 3G 的堆信息文件,显然一般的 Window 系统没有这么大的内存,必须借助高配置的 Linux。当然我们可以借助 X-Window 把 Linux 上的图形导入到 Window。我们考虑用下面几种工具打开该文件:
- Visual VM
- IBM HeapAnalyzer
- JDK 自带的 Hprof 工具
使用这些工具时为了确保加载速度,建议设置最大内存为 6G。使用后发现,这些工具都无法直观地观察到内存泄漏,Visual VM 虽能观察到对象大小,但看不到调用堆栈;HeapAnalyzer 虽然能看到调用堆栈,却无法正确打开一个 3G 的文件。因此,我们又选用了 Eclipse 专门的静态内存分析工具:Mat。
4. 分析内存泄漏
通过 Mat 我们能清楚地看到,哪些对象被怀疑为内存泄漏,哪些对象占的空间最大及对象的调用关系。针对本案,在 ThreadLocal 中有很多的 JbpmContext 实例,经过调查是 JBPM 的 Context 没有关闭所致。
另,通过 Mat 或 JMX 我们还可以分析线程状态,可以观察到线程被阻塞在哪个对象上,从而判断系统的瓶颈。
5. 回归问题
Q:为什么崩溃前垃圾回收的时间越来越长?
A: 根据内存模型和垃圾回收算法,垃圾回收分两部分:内存标记、清除(复制),标记部分只要内存大小固定时间是不变的,变的是复制部分,因为每次垃圾回收都有一些回收不掉的内存,所以增加了复制量,导致时间延长。所以,垃圾回收的时间也可以作为判断内存泄漏的依据
Q:为什么 Full GC 的次数越来越多?
A:因此内存的积累,逐渐耗尽了年老代的内存,导致新对象分配没有更多的空间,从而导致频繁的垃圾回收
Q: 为什么年老代占用的内存越来越大?
A: 因为年轻代的内存无法被回收,越来越多地被 Copy 到年老代
三、性能调优
除了上述内存泄漏外,我们还发现 CPU 长期不足 3%,系统吞吐量不够,针对 8core×16G、64bit 的 Linux 服务器来说,是严重的资源浪费。
在 CPU 负载不足的同时,偶尔会有用户反映请求的时间过长,我们意识到必须对程序及 JVM 进行调优。从以下几个方面进行:
- 线程池:解决用户响应时间长的问题
- 连接池
- JVM 启动参数:调整各代的内存比例和垃圾回收算法,提高吞吐量
- 程序算法:改进程序逻辑算法提高性能
1.Java 线程池(java.util.concurrent.ThreadPoolExecutor)
大多数 JVM6 上的应用采用的线程池都是 JDK 自带的线程池,之所以把成熟的 Java 线程池进行罗嗦说明,是因为该线程池的行为与我们想象的有点出入。Java 线程池有几个重要的配置参数:
- corePoolSize:核心线程数(最新线程数)
- maximumPoolSize:最大线程数,超过这个数量的任务会被拒绝,用户可以通过 RejectedExecutionHandler 接口自定义处理方式
- keepAliveTime:线程保持活动的时间
- workQueue:工作队列,存放执行的任务
Java 线程池需要传入一个 Queue 参数(workQueue)用来存放执行的任务,而对 Queue 的不同选择,线程池有完全不同的行为:
SynchronousQueue:
一个无容量的等待队列,一个线程的insert操作必须等待另一线程的remove操作,采用这个Queue线程池将会为每个任务分配一个新线程
LinkedBlockingQueue :
无界队列,采用该Queue,线程池将忽略
maximumPoolSize 参数,仅用 corePoolSize 的线程处理所有的任务,未处理的任务便在LinkedBlockingQueue中排队
ArrayBlockingQueue: 有界队列,在有界队列和
maximumPoolSize 的作用下,程序将很难被调优:更大的 Queue 和小的 maximumPoolSize 将导致 CPU 的低负载;小的 Queue 和大的池,Queue 就没起动应有的作用。
其实我们的要求很简单,希望线程池能跟连接池一样,能设置最小线程数、最大线程数,当最小数 <任务<最大数时,应该分配新的线程处理;当任务> 最大数时,应该等待有空闲线程再处理该任务。
但线程池的设计思路是,任务应该放到 Queue 中,当 Queue 放不下时再考虑用新线程处理,如果 Queue 满且无法派生新线程,就拒绝该任务。设计导致 “先放等执行”、“放不下再执行”、“拒绝不等待”。所以,根据不同的 Queue 参数,要提高吞吐量不能一味地增大 maximumPoolSize。
当然,要达到我们的目标,必须对线程池进行一定的封装,幸运的是 ThreadPoolExecutor 中留了足够的自定义接口以帮助我们达到目标。我们封装的方式是:
- 以 SynchronousQueue 作为参数,使 maximumPoolSize 发挥作用,以防止线程被无限制的分配,同时可以通过提高 maximumPoolSize 来提高系统吞吐量
- 自定义一个 RejectedExecutionHandler,当线程数超过 maximumPoolSize 时进行处理,处理方式为隔一段时间检查线程池是否可以执行新 Task,如果可以把拒绝的 Task 重新放入到线程池,检查的时间依赖 keepAliveTime 的大小。
2. 连接池(org.apache.commons.dbcp.BasicDataSource)
在使用 org.apache.commons.dbcp.BasicDataSource 的时候,因为之前采用了默认配置,所以当访问量大时,通过 JMX 观察到很多 Tomcat 线程都阻塞在 BasicDataSource 使用的 Apache ObjectPool 的锁上,直接原因当时是因为 BasicDataSource 连接池的最大连接数设置的太小,默认的 BasicDataSource 配置,仅使用 8 个最大连接。
我还观察到一个问题,当较长的时间不访问系统,比如 2 天,DB 上的 Mysql 会断掉所以的连接,导致连接池中缓存的连接不能用。为了解决这些问题,我们充分研究了 BasicDataSource,发现了一些优化的点:
- Mysql 默认支持 100 个链接,所以每个连接池的配置要根据集群中的机器数进行,如有 2 台服务器,可每个设置为 60
- initialSize:参数是一直打开的连接数
- minEvictableIdleTimeMillis:该参数设置每个连接的空闲时间,超过这个时间连接将被关闭
- timeBetweenEvictionRunsMillis:后台线程的运行周期,用来检测过期连接
- maxActive:最大能分配的连接数
- maxIdle:最大空闲数,当连接使用完毕后发现连接数大于 maxIdle,连接将被直接关闭。只有 initialSize < x < maxIdle 的连接将被定期检测是否超期。这个参数主要用来在峰值访问时提高吞吐量。
- initialSize 是如何保持的?经过研究代码发现,BasicDataSource 会关闭所有超期的连接,然后再打开 initialSize 数量的连接,这个特性与 minEvictableIdleTimeMillis、timeBetweenEvictionRunsMillis 一起保证了所有超期的 initialSize 连接都会被重新连接,从而避免了 Mysql 长时间无动作会断掉连接的问题。
3.JVM 参数
在 JVM 启动参数中,可以设置跟内存、垃圾回收相关的一些参数设置,默认情况不做任何设置 JVM 会工作的很好,但对一些配置很好的 Server 和具体的应用必须仔细调优才能获得最佳性能。通过设置我们希望达到一些目标:
- GC 的时间足够的小
- GC 的次数足够的少
- 发生 Full GC 的周期足够的长
前两个目前是相悖的,要想 GC 时间小必须要一个更小的堆,要保证 GC 次数足够少,必须保证一个更大的堆,我们只能取其平衡。
(1)针对 JVM 堆的设置,一般可以通过 - Xms -Xmx 限定其最小、最大值,为了防止垃圾收集器在最小、最大之间收缩堆而产生额外的时间,我们通常把最大、最小设置为相同的值
(2)年轻代和年老代将根据默认的比例(1:2)分配堆内存,可以通过调整二者之间的比率 NewRadio 来调整二者之间的大小,也可以针对回收代,比如年轻代,通过 -XX:newSize -XX:MaxNewSize 来设置其绝对大小。同样,为了防止年轻代的堆收缩,我们通常会把 - XX:newSize -XX:MaxNewSize 设置为同样大小
(3)年轻代和年老代设置多大才算合理?这个我问题毫无疑问是没有答案的,否则也就不会有调优。我们观察一下二者大小变化有哪些影响
- 更大的年轻代必然导致更小的年老代,大的年轻代会延长普通 GC 的周期,但会增加每次 GC 的时间;小的年老代会导致更频繁的 Full GC
- 更小的年轻代必然导致更大年老代,小的年轻代会导致普通 GC 很频繁,但每次的 GC 时间会更短;大的年老代会减少 Full GC 的频率
- 如何选择应该依赖应用程序对象生命周期的分布情况:如果应用存在大量的临时对象,应该选择更大的年轻代;如果存在相对较多的持久对象,年老代应该适当增大。但很多应用都没有这样明显的特性,在抉择时应该根据以下两点:(A)本着 Full GC 尽量少的原则,让年老代尽量缓存常用对象,JVM 的默认比例 1:2 也是这个道理 (B)通过观察应用一段时间,看其他在峰值时年老代会占多少内存,在不影响 Full GC 的前提下,根据实际情况加大年轻代,比如可以把比例控制在 1:1。但应该给年老代至少预留 1/3 的增长空间
(4)在配置较好的机器上(比如多核、大内存),可以为年老代选择并行收集算法: -XX:+UseParallelOldGC ,默认为 Serial 收集
(5)线程堆栈的设置:每个线程默认会开启 1M 的堆栈,用于存放栈帧、调用参数、局部变量等,对大多数应用而言这个默认值太了,一般 256K 就足用。理论上,在内存不变的情况下,减少每个线程的堆栈,可以产生更多的线程,但这实际上还受限于操作系统。
(4)可以通过下面的参数打 Heap Dump 信息
- -XX:HeapDumpPath
- -XX:+PrintGCDetails
- -XX:+PrintGCTimeStamps
- -Xloggc:/usr/aaa/dump/heap_trace.txt
通过下面参数可以控制 OutOfMemoryError 时打印堆的信息
- -XX:+HeapDumpOnOutOfMemoryError
请看一下一个时间的 Java 参数配置:(服务器:Linux 64Bit,8Core×16G)
JAVA_OPTS="$JAVA_OPTS -server -Xms3G -Xmx3G -Xss256k -XX:PermSize=128m -XX:MaxPermSize=128m -XX:+UseParallelOldGC -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/aaa/dump -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:/usr/aaa/dump/heap_trace.txt -XX:NewSize=1G -XX:MaxNewSize=1G"
经过观察该配置非常稳定,每次普通 GC 的时间在 10ms 左右,Full GC 基本不发生,或隔很长很长的时间才发生一次
通过分析 dump 文件可以发现,每个 1 小时都会发生一次 Full GC,经过多方求证,只要在 JVM 中开启了 JMX 服务,JMX 将会 1 小时执行一次 Full GC 以清除引用,关于这点请参考附件文档。
4. 程序算法调优:本次不作为重点
参考资料:
http://java.sun.com/javase/technologies/hotspot/gc/gc_tuning_6.html
来源:http://blog.csdn.net/chen77716/article/details/5695893
=======================================================================================
调优方法
一切都是为了这一步,调优,在调优之前,我们需要记住下面的原则:
1、多数的 Java 应用不需要在服务器上进行 GC 优化;
2、多数导致 GC 问题的 Java 应用,都不是因为我们参数设置错误,而是代码问题;
3、在应用上线之前,先考虑将机器的 JVM 参数设置到最优(最适合);
4、减少创建对象的数量;
5、减少使用全局变量和大对象;
6、GC 优化是到最后不得已才采用的手段;
7、在实际使用中,分析 GC 情况优化代码比优化 GC 参数要多得多;
GC 优化的目的有两个(http://www.360doc.com/content/13/0305/10/15643_269388816.shtml):
1、将转移到老年代的对象数量降低到最小;
2、减少 full GC 的执行时间;
为了达到上面的目的,一般地,你需要做的事情有:
1、减少使用全局变量和大对象;
2、调整新生代的大小到最合适;
3、设置老年代的大小为最合适;
4、选择合适的 GC 收集器;
在上面的 4 条方法中,用了几个 “合适”,那究竟什么才算合适,一般的,请参考上面“收集器搭配” 和“启动内存分配”两节中的建议。但这些建议不是万能的,需要根据您的机器和应用情况进行发展和变化,实际操作中,可以将两台机器分别设置成不同的 GC 参数,并且进行对比,选用那些确实提高了性能或减少了 GC 时间的参数。
真正熟练的使用 GC 调优,是建立在多次进行 GC 监控和调优的实战经验上的,进行监控和调优的一般步骤为:
1,监控 GC 的状态
使用各种 JVM 工具,查看当前日志,分析当前 JVM 参数设置,并且分析当前堆内存快照和 gc 日志,根据实际的各区域内存划分和 GC 执行时间,觉得是否进行优化;
2,分析结果,判断是否需要优化
如果各项参数设置合理,系统没有超时日志出现,GC 频率不高,GC 耗时不高,那么没有必要进行 GC 优化;如果 GC 时间超过 1-3 秒,或者频繁 GC,则必须优化;
注:如果满足下面的指标,则一般不需要进行 GC:
Minor GC 执行时间不到 50ms;
Minor GC 执行不频繁,约 10 秒一次;
Full GC 执行时间不到 1s;
Full GC 执行频率不算频繁,不低于 10 分钟 1 次;
3,调整 GC 类型和内存分配
如果内存分配过大或过小,或者采用的 GC 收集器比较慢,则应该优先调整这些参数,并且先找 1 台或几台机器进行 beta,然后比较优化过的机器和没有优化的机器的性能对比,并有针对性的做出最后选择;
4,不断的分析和调整
通过不断的试验和试错,分析并找到最合适的参数
5,全面应用参数
如果找到了最合适的参数,则将这些参数应用到所有服务器,并进行后续跟踪。
调优实例
上面的内容都是纸上谈兵,下面我们以一些真实例子来进行说明:
实例 1:
笔者昨日发现部分开发测试机器出现异常:java.lang.OutOfMemoryError: GC overhead limit exceeded,这个异常代表:
GC 为了释放很小的空间却耗费了太多的时间,其原因一般有两个:1,堆太小,2,有死循环或大对象;
笔者首先排除了第 2 个原因,因为这个应用同时是在线上运行的,如果有问题,早就挂了。所以怀疑是这台机器中堆设置太小;
使用 ps -ef |grep "java" 查看,发现:该应用的堆区设置只有 768m,而机器内存有 2g,机器上只跑这一个 java 应用,没有其他需要占用内存的地方。另外,这个应用比较大,需要占用的内存也比较多;
笔者通过上面的情况判断,只需要改变堆中各区域的大小设置即可,于是改成下面的情况:
跟踪运行情况发现,相关异常没有再出现;
实例 2:(http://www.360doc.com/content/13/0305/10/15643_269388816.shtml)
一个服务系统,经常出现卡顿,分析原因,发现 Full GC 时间太长:
jstat -gcutil:
S0 S1 E O P YGC YGCT FGC FGCT GCT
12.16 0.00 5.18 63.78 20.32 54 2.047 5 6.946 8.993
分析上面的数据,发现 Young GC 执行了 54 次,耗时 2.047 秒,每次 Young GC 耗时 37ms,在正常范围,而 Full GC 执行了 5 次,耗时 6.946 秒,每次平均 1.389s,数据显示出来的问题是:Full GC 耗时较长,分析该系统的是指发现,NewRatio=9,也就是说,新生代和老生代大小之比为 1:9,这就是问题的原因:
1,新生代太小,导致对象提前进入老年代,触发老年代发生 Full GC;
2,老年代较大,进行 Full GC 时耗时较大;
优化的方法是调整 NewRatio 的值,调整到 4,发现 Full GC 没有再发生,只有 Young GC 在执行。这就是把对象控制在新生代就清理掉,没有进入老年代(这种做法对一些应用是很有用的,但并不是对所有应用都要这么做)
实例 3:
一应用在性能测试过程中,发现内存占用率很高,Full GC 频繁,使用 sudo -u admin -H jmap -dump:format=b,file = 文件名. hprof pid 来 dump 内存,生成 dump 文件,并使用 Eclipse 下的 mat 差距进行分析,发现:
从图中可以看出,这个线程存在问题,队列 LinkedBlockingQueue 所引用的大量对象并未释放,导致整个线程占用内存高达 378m,此时通知开发人员进行代码优化,将相关对象释放掉即可。
原文二地址 http://unixboy.iteye.com/blog/174173
- 堆大小设置
JVM 中最大堆大小有三方面限制:相关操作系统的数据模型(32-bt 还是 64-bit)限制;系统的可用虚拟内存限制;系统的可用物理内存限制。32 位系统下,一般限制在 1.5G~2G;64 为操作系统对内存无限制。我在 Windows Server 2003 系统,3.5G 物理内存,JDK5.0 下测试,最大可设置为 1478m。
典型设置:- java -Xmx3550m -Xms3550m -Xmn2g -Xss128k
-Xmx3550m:设置 JVM 最大可用内存为 3550M。
-Xms3550m:设置 JVM 初始内存为 3550m。此值可以设置与 - Xmx 相同,以避免每次垃圾回收完成后 JVM 重新分配内存。
-Xmn2g:设置年轻代大小为 2G。整个 JVM 内存大小 = 年轻代大小 + 年老代大小 + 持久代大小。持久代一般固定大小为 64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun 官方推荐配置为整个堆的 3/8。
-Xss128k:设置每个线程的堆栈大小。JDK5.0 以后每个线程堆栈大小为 1M,以前每个线程堆栈大小为 256K。更具应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在 3000~5000 左右。 - java -Xmx3550m -Xms3550m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=16m -XX:MaxTenuringThreshold=0
-XX:NewRatio=4: 设置年轻代(包括 Eden 和两个 Survivor 区)与年老代的比值(除去持久代)。设置为 4,则年轻代与年老代所占比值为 1:4,年轻代占整个堆栈的 1/5
-XX:SurvivorRatio=4:设置年轻代中 Eden 区与 Survivor 区的大小比值。设置为 4,则两个 Survivor 区与一个 Eden 区的比值为 2:4,一个 Survivor 区占整个年轻代的 1/6
-XX:MaxPermSize=16m: 设置持久代大小为 16m。
-XX:MaxTenuringThreshold=0:设置垃圾最大年龄。如果设置为 0 的话,则年轻代对象不经过 Survivor 区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在 Survivor 区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概论。
- java -Xmx3550m -Xms3550m -Xmn2g -Xss128k
- 回收器选择
- JVM 回收器分为三类:串行收集器、并行收集器、并发收集器,但是串行收集器只适用于小数据量的情况,所以这里的选择主要针对并行收集器和并发收集器。默认情况下,JDK5.0 以前都是使用串行收集器,如果想使用其他收集器需要在启动时加入相应参数。JDK5.0 以后,JVM 会根据当前系统配置进行判断。
- 吞吐量优先的并行收集器
如上文所述,并行收集器主要以到达一定的吞吐量为目标,适用于科学技术和后台处理等。
典型配置:- java -Xmx3800m -Xms3800m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20
-XX:+UseParallelGC:选择垃圾收集器为并行收集器。此配置仅对年轻代有效。即上述配置下,年轻代使用并发收集,而年老代仍旧使用串行收集。
-XX:ParallelGCThreads=20:配置并行收集器的线程数,即:同时多少个线程一起进行垃圾回收。此值最好配置与处理器数目相等。 - java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20 -XX:+UseParallelOldGC
-XX:+UseParallelOldGC:配置年老代垃圾收集方式为并行收集。JDK6.0 支持对年老代并行收集。 - java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100
-XX:MaxGCPauseMillis=100: 设置每次年轻代垃圾回收的最长时间,如果无法满足此时间,JVM 会自动调整年轻代大小,以满足此值。 - java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100 -XX:+UseAdaptiveSizePolicy
-XX:+UseAdaptiveSizePolicy:设置此选项后,并行收集器会自动选择年轻代区大小和相应的 Survivor 区比例,以达到目标系统规定的最低相应时间或者收集频率等,此值建议使用并行收集器时,一直打开。
- java -Xmx3800m -Xms3800m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20
- 响应时间优先的并发收集器
如上文所述,并发收集器主要是保证系统的响应时间,减少垃圾收集时的停顿时间。适用于应用服务器、电信领域等。
典型配置:- java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
-XX:+UseConcMarkSweepGC:设置年老代为并发收集。测试中配置这个以后,-XX:NewRatio=4 的配置失效了,原因不明。所以,此时年轻代大小最好用 - Xmn 设置。
-XX:+UseParNewGC: 设置年轻代为并行收集。可与 CMS 收集同时使用。JDK5.0 以上,JVM 会根据系统配置自行设置,所以无需再设置此值。 - java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseConcMarkSweepGC -XX:CMSFullGCsBeforeCompaction=5 -XX:+UseCMSCompactAtFullCollection
-XX:CMSFullGCsBeforeCompaction:由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生 “碎片”,使得运行效率降低。此值设置运行多少次 GC 以后对内存空间进行压缩、整理。
-XX:+UseCMSCompactAtFullCollection:打开对年老代的压缩。可能会影响性能,但是可以消除碎片
- java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
- 吞吐量优先的并行收集器
- 辅助信息
JVM 提供了大量命令行参数,打印信息,供调试使用。主要有以下一些:- -XX:+PrintGC
输出形式:[GC 118250K->113543K(130112K), 0.0094143 secs][Full GC 121376K->10414K(130112K), 0.0650971 secs]
- -XX:+PrintGCDetails
输出形式:[GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs][GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured: 112761K->10414K(121024K), 0.0433488 secs] 121376K->10414K(130112K), 0.0436268 secs]
- -XX:+PrintGCTimeStamps -XX:+PrintGC:PrintGCTimeStamps 可与上面两个混合使用
输出形式:11.851: [GC 98328K->93620K(130112K), 0.0082960 secs] - -XX:+PrintGCApplicationConcurrentTime: 打印每次垃圾回收前,程序未中断的执行时间。可与上面混合使用
输出形式:Application time: 0.5291524 seconds - -XX:+PrintGCApplicationStoppedTime:打印垃圾回收期间程序暂停的时间。可与上面混合使用
输出形式:Total time for which application threads were stopped: 0.0468229 seconds - -XX:PrintHeapAtGC: 打印 GC 前后的详细堆栈信息
输出形式:
34.702: [GC {Heap before gc invocations=7:
def new generation total 55296K, used 52568K [0x1ebd0000, 0x227d0000, 0x227d0000)
eden space 49152K, 99% used [0x1ebd0000, 0x21bce430, 0x21bd0000)
from space 6144K, 55% used [0x221d0000, 0x22527e10, 0x227d0000)
to space 6144K, 0% used [0x21bd0000, 0x21bd0000, 0x221d0000)
tenured generation total 69632K, used 2696K [0x227d0000, 0x26bd0000, 0x26bd0000)
the space 69632K, 3% used [0x227d0000, 0x22a720f8, 0x22a72200, 0x26bd0000)
compacting perm gen total 8192K, used 2898K [0x26bd0000, 0x273d0000, 0x2abd0000)
the space 8192K, 35% used [0x26bd0000, 0x26ea4ba8, 0x26ea4c00, 0x273d0000)
ro space 8192K, 66% used [0x2abd0000, 0x2b12bcc0, 0x2b12be00, 0x2b3d0000)
rw space 12288K, 46% used [0x2b3d0000, 0x2b972060, 0x2b972200, 0x2bfd0000)
34.735: [DefNew: 52568K->3433K(55296K), 0.0072126 secs] 55264K->6615K(124928K)Heap after gc invocations=8:
def new generation total 55296K, used 3433K [0x1ebd0000, 0x227d0000, 0x227d0000)
eden space 49152K, 0% used [0x1ebd0000, 0x1ebd0000, 0x21bd0000)
from space 6144K, 55% used [0x21bd0000, 0x21f2a5e8, 0x221d0000)
to space 6144K, 0% used [0x221d0000, 0x221d0000, 0x227d0000)
tenured generation total 69632K, used 3182K [0x227d0000, 0x26bd0000, 0x26bd0000)
the space 69632K, 4% used [0x227d0000, 0x22aeb958, 0x22aeba00, 0x26bd0000)
compacting perm gen total 8192K, used 2898K [0x26bd0000, 0x273d0000, 0x2abd0000)
the space 8192K, 35% used [0x26bd0000, 0x26ea4ba8, 0x26ea4c00, 0x273d0000)
ro space 8192K, 66% used [0x2abd0000, 0x2b12bcc0, 0x2b12be00, 0x2b3d0000)
rw space 12288K, 46% used [0x2b3d0000, 0x2b972060, 0x2b972200, 0x2bfd0000)
}
, 0.0757599 secs] - -Xloggc:filename: 与上面几个配合使用,把相关日志信息记录到文件以便分析。
- -XX:+PrintGC
- 常见配置汇总
- 堆设置
- -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: 设置持久代大小
- 收集器设置
- -XX:+UseSerialGC: 设置串行收集器
- -XX:+UseParallelGC: 设置并行收集器
- -XX:+UseParalledlOldGC: 设置并行年老代收集器
- -XX:+UseConcMarkSweepGC: 设置并发收集器
- 垃圾回收统计信息
- -XX:+PrintGC
- -XX:+PrintGCDetails
- -XX:+PrintGCTimeStamps
- -Xloggc:filename
- 并行收集器设置
- -XX:ParallelGCThreads=n: 设置并行收集器收集时使用的 CPU 数。并行收集线程数。
- -XX:MaxGCPauseMillis=n: 设置并行收集最大暂停时间
- -XX:GCTimeRatio=n: 设置垃圾回收时间占程序运行时间的百分比。公式为 1/(1+n)
- 并发收集器设置
- -XX:+CMSIncrementalMode: 设置为增量模式。适用于单 CPU 情况。
- -XX:ParallelGCThreads=n: 设置并发收集器年轻代收集方式为并行收集时,使用的 CPU 数。并行收集线程数。
- 堆设置
四、调优总结
- 年轻代大小选择
- 响应时间优先的应用:尽可能设大,直到接近系统的最低响应时间限制(根据实际情况选择)。在此种情况下,年轻代收集发生的频率也是最小的。同时,减少到达年老代的对象。
- 吞吐量优先的应用:尽可能的设置大,可能到达 Gbit 的程度。因为对响应时间没有要求,垃圾收集可以并行进行,一般适合 8CPU 以上的应用。
- 年老代大小选择
- 响应时间优先的应用:年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数。如果堆设置小了,可以会造成内存碎片、高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间。最优化的方案,一般需要参考以下数据获得:
- 并发垃圾收集信息
- 持久代并发收集次数
- 传统 GC 信息
- 花在年轻代和年老代回收上的时间比例
- 吞吐量优先的应用:一般吞吐量优先的应用都有一个很大的年轻代和一个较小的年老代。原因是,这样可以尽可能回收掉大部分短期对象,减少中期的对象,而年老代尽存放长期存活对象。
- 响应时间优先的应用:年老代使用并发收集器,所以其大小需要小心设置,一般要考虑并发会话率和会话持续时间等一些参数。如果堆设置小了,可以会造成内存碎片、高回收频率以及应用暂停而使用传统的标记清除方式;如果堆大了,则需要较长的收集时间。最优化的方案,一般需要参考以下数据获得:
- 较小堆引起的碎片问题
因为年老代的并发收集器使用标记、清除算法,所以不会对堆进行压缩。当收集器回收时,他会把相邻的空间进行合并,这样可以分配给较大的对象。但是,当堆空间较小时,运行一段时间以后,就会出现 “碎片”,如果并发收集器找不到足够的空间,那么并发收集器将会停止,然后使用传统的标记、清除方式进行回收。如果出现 “碎片”,可能需要进行如下配置:- -XX:+UseCMSCompactAtFullCollection:使用并发收集器时,开启对年老代的压缩。
- -XX:CMSFullGCsBeforeCompaction=0:上面配置开启的情况下,这里设置多少次 Full GC 后,对年老代进行压缩
全文完