JVM GC调优总结

 

一、基本收集算法

 


1. 复制:将堆内分成两个相同空间,从根(ThreadLocal的对象,静态对象)开始访问每一个关联的活跃对象,将空间A的活跃对象全部复制到空间B,然后一次性回收整个空间A。
因为只访问活跃对象,将所有活动对象复制走之后就清空整个空间,不用去访问死对象,所以遍历空间的成本较小,但需要巨大的复制成本和较多的内存。

优点:只访问活跃对象,遍历快

缺点:需要内存比较多,浪费一部分内存,不适合年老代
2. 标记清除(mark-sweep):收集器先从根开始访问所有活跃对象,标记为活跃对象。然后再遍历一次整个内存区域,把所有没有标记活跃的对象进行回收处理。该算法遍历整个空间的成本较大暂停时间随空间大小线性增大,而且整理后堆里的碎片很多。

优点:不浪费内存

缺点:遍历2次,2次标记效率都不高,内存碎片多

3. 标记整理(mark-sweep-compact):综合了上述两者的做法和优点,先标记活跃对象,然后将其合并成较大的内存块。

移动了活跃对象,回收后留下完整内存块

二 JVM GC收集器


1.Serial收集器-XX:+UseSerialGC 复制收集算法
  单线程收集器,收集时会暂停所有工作线程(我们将这件事情称之为Stop The World,下称STW),使用复制收集算法,虚拟机运行在Client模式时的默认新生代收集器。

2.ParNew收集器-XX:+UseParNewGC 复制收集算法 多线程执行
  ParNew收集器就是Serial的多线程版本,除了使用多条收集线程外,其余行为包括算法、STW、对象分配规则、回收策略等都与Serial收集器一摸一样。对应的这种收集器是虚拟机运行在Server模式的默认新生代收集器,在单CPU的环境中,ParNew收集器并不会比Serial收集器有更好的效果。

3.Parallel Scavenge收集器-XX:+UseParallelGC 复制算法,吞吐量最大化
  Parallel Scavenge收集器(下称PS收集器)也是一个多线程收集器,也是使用复制算法,但它的对象分配规则与回收策略都与ParNew收集器有所不同,它是以吞吐量最大化(即GC时间占总运行时间最小)为目标的收集器实现,它允许较长时间的STW换取总吞吐量最大化。

4.Serial Old收集器-XX:+UseSerialGC 标记整理算法
  Serial Old是单线程收集器,使用标记-整理算法,是老年代的收集器,上面三种都是使用在新生代收集器。

5.Parallel Old收集器-XX:+UseParallelOldGC 标记整理算法多线程执行
  老年代版本吞吐量优先收集器,使用多线程和标记-整理算法,JVM 1.6提供,在此之前,新生代使用了PS收集器的话,老年代除Serial Old外别无选择,因为PS无法与CMS收集器配合工作。

6.CMS(Concurrent Mark Sweep)收集器XX:+UseconcMarkSweepGC 标记,置换 不压缩 算法

压缩需要加参数-XX:CMSFullGCsBeforeCompaction(多少次FullGC后压缩内存) 或者-XX+UseCMSCompactAtFullCollection(CMS后压缩内存)
CMS收集器使用的是标记-清除算法,也就是说它在运行期间会产生空间碎片,所以虚拟机提供了参数开启CMS收集结束后再进行一次内存压缩。

 

CMS大体过程为:

 

(1)并发gc会先暂停jvm运行,然后标志应用中的强可达对象;init-mark,stop the world
(2)开始并发标志引用强可达对象的对象;这时程序还在运行,concurrent-mark
(3)因为程序正在运行,这时重新标志一下引用被修改的对象(这时jvm会再次暂停)rescan
(4)重新标记后,堆中所有活动对象被标记出来了,然后开发开始回收堆中的垃圾对象;sweep

(5)该算法不进行compact,而是在内存中建立一个空闲内存的链表,下次在old区分配空间时,会先找到一个符合条件的空闲空间给对象,该方式会影响old区和young区的对象空间分配,影响young区主要是由于有些young区的对象需要转至old区.

三、JVM设置需要注意的地方

1.-Xmn 与-XX:NewRatio 不要同时设置

 

2.-Xss一定要设置JDK5.0以后每个线程堆栈大小为1M,对一般的应用来说,256K足够了,如果发生StackOverFlow错误,那么加大这个值也没用。

jvm 栈 和本地方法栈可能存在的异常:

StackOverFlowError:线程请求的栈深度>虚拟机所允许的深度
OutOfMemory:当扩展时无法申请到足够的内存(大部分虚拟机都允许动态扩展,java虚拟机规范也允许固定长度)

 

3.设置-XX:+DisableExplicitGC,避免RMI或应用显示调用System.gc().如果有此类调用gc日志中会含有System标志。

修正:

1.jdk6 u32之前存在一个bug,某些情况下,cms GC 不回收Dirent byte buffer,堆外内存。

2.使用这个参数可能会导致cms GC触发之前,堆外内存过大时,物理内存或地址空间不足,出现OOM错误。

因此建议:

1.不使用这个参数,改为:-XX:+ExplicitGCInvokesConcurrent

2.配置堆外内存最大空间:-XX:MaxDirectMemorySize。不配置时,默认与-Xmx值相同。可能造成OOM.

 

4.-XX:MaxTenuringThreshold 对象最多!经过多少次minor GC后进入年老代。设置为0,则不经过survior拷贝,直接进入年老代。增大此值会增加对象在Minor GC被回收的几率,减小此值会增加对象进入年老代的几率。注意最多,有可能不经过那么多次,比如

4.1.大的数组对象,如果在survivor空间中相同年龄所有对象大小的累计值大于survivor空间的一半,大于或等于个年龄的对象就可以直接进入老年代,无需达到MaxTenuringThreshold中要求的年龄

4.2.配置了-XX:PretenureSizeThreshold 参数,则大于指定byte的对象直接进入年老代分配。

 

5.持久代GC时回收无引用的废弃常量与无用类。无用类需满足以下三个条件:

5.1.该类所有的实例都已经被GC,也就是JVM中不存在该Class的任何实例。

5.2.加载该类的ClassLoader已经被GC。

5.3.该类对应的java.lang.Class 对象没有在任何地方被引用,如不能在任何地方通过反射访问该类的方法。

是否对类进行回收可使用-XX:+ClassUnloading参数进行控制,还可以使用-verbose:class或者-XX:+TraceClassLoading、-XX:+TraceClassUnLoading查看类加载、卸载信息。

在大量使用反射、动态代理、CGLib等bytecode框架、动态生成JSP以及OSGi这类频繁自定义ClassLoader的场景都需要JVM具备类卸载的支持以保证永久代不会溢出

6 -XX:CMSInitiatingOccupancyFraction=70以上 堆内存达到多少比例时,触发CMS。需要与-XX:+UseCMSInitiatingOccupancyOnly 同时使用以保证CMS不自动触发,否则不到比例也可能触发CMS.

计算公式一:CMSInitiatingOccupancyFraction <=((Xmx-Xmn)-(Xmn-Xmn/(SurvivorRatior+2)))/(Xmx-Xmn)*100

考虑了2个救助区中一个servior不会使用的情况。

计算公式二(Xmx-Xmn)*(100-CMSInitiatingOccupancyFraction)/100>=Xmn

统一考虑年轻代。

满足以上2个计算公式,则不会出现 concurrent model fail。及不会出现并行回收失败,降级为虚拟机串行回收(SerialGC)的情况。

7.CMS碎片问题

因为年老代的并发收集器使用标记,清除算法,所以不会对堆进行压缩.当收集器回收时,他会把相邻的空间进行合并,这样可以分配给较大的对象.但是,当堆空间较小时,运行一段时间以后,就会出现"碎片",如果并发收集器找不到足够的空间,那么并发收集器将会停止,然后使用传统的标记,清除方式进行回收.如果出现"碎片",可能需要进行如下配置:

-XX:+UseCMSCompactAtFullCollection:使用并发收集器时,开启对年老代的压缩.

-XX:CMSFullGCsBeforeCompaction=0:上面配置开启的情况下,这里设置多少次Full GC后,对年老代进行压缩

 

8.XMX和XMS设置一样大,MaxPermSize和MinPermSize设置一样大,这样可以减轻伸缩堆大小带来的压力,还真有人不这么干

 

9:promotion failed:

救助空间不足,要放到老生代,但老生代空闲空间存在碎片,导致没有足够大的连续空间来存放新生代对象的升级时,机会触发promotion failed

解决方法:

9.1.增大救助空间、增大年老代

增大救助空间就是调小-XX:SurvivorRatio这个参数是Eden区和Survivor区的大小比值,默认是32

增大年老代,调大-Xmn参数,增加新生代,或调大-XX:NewRatio,减少新生代比例,增大年老代。

9.2..去掉救助空间(不赞成)

调大-XX:SurvivorRatio到非常大的数值65536

10:Concurrent Mode Failure

并发收集器在应用运行时进行收集,所以需要保证堆在垃圾回收的这段时间有足够的空间供程序使用,否则,垃圾回收还未完成,堆空间先满了。这种情况下将会发生“并发模式失败”,此时整个应用将会暂停,进行垃圾回收

解决方法:

10.1.满足第6点的公式,可以避免这个问题。

10.2.调小CMSMaxAbortablePrecleanTime的值 尽快回收

11.CMS默认启动的回收线程数目是(ParallelGCThreads + 3)/4)

也可以通过:-XX:ParallelCMSThreads=20 设置

12.为了减少CMS GC第二次暂停的时间,开启并行remark:-XX:+CMSParallelRemarkEnabled。如果remark还是过长的话,可以开启-XX:+CMSScavengeBeforeRemark选项,强制remark之前开始一次minor gc,减少remark的暂停时间,但是在remark之后也将立即开始又一次minor gc。

13.+CMSPermGenSweepingEnabled -XX:+CMSClassUnloadingEnabled CMS回收持久代

14.FullGC的触发条件

14.1.old空间不足 达到比例(CMSInitiatingOccupancyFraction) 或 promotion failed 或 current mode failed(这个会很悲剧)

14.2.Perm空间不足 92%

14.3.显示调用System.GC, RMI等的定时触发

14.4.YGC时的悲观策略

14.5.dump live的内存信息时(jmap –dump:live)

15。YGC时的悲观策略

 

15.1、在YGC执行前,min(目前新生代已使用的大小,之前平均晋升到old的大小中的较小值) > 旧生代剩余空间大小 ? 不执行YGC,直接执行Full GC: 执行YGC;
15.2、在YGC执行后,平均晋升到old的大小 > 旧生代剩余空间大小 ? 触发Full GC : 什么都不做

 

CMSGc时,旧生代剩余空间需要考虑CMSInitiatingOccupancyFraction

在Minor GC触发时,会检测之前每次晋升到老年代的平均大小是否大于老年代的剩余空间,如果大于,改为直接进行一次Full GC,如果小于则查看HandlePromotionFailure设置看看是否允许担保失败,如果允许,那仍然进行Minor GC,如果不允许,则也要改为进行一次Full GC

16.导致OOM的原因

1.GC overhead limit exceeded
2.Java Heap Space
3.Unable to create new native thread
4.PermGen Space
5.Direct buffer memory
6.request {} bytes for {}. Out of swap space?

 

四 案例及日志

CMS GC的一个具体案例:

 

具体过程为:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
2013-11-27T04:00:12.819+0800:38892.743: [GC [1CMS-initial-mark: 1547313K(2146304K)] 1734957K(4023680K),0.1390860secs] [Times: user=0.14sys=0.00, real=0.14secs]
2013-11-27T04:00:12.958+0800:38892.883: [CMS-concurrent-mark-start]
2013-11-27T04:00:19.231+0800:38899.155: [CMS-concurrent-mark:6.255/6.272secs] [Times: user=8.49sys=1.57, real=6.27secs]
2013-11-27T04:00:19.231+0800:38899.155: [CMS-concurrent-preclean-start]
2013-11-27T04:00:19.250+0800:38899.175: [CMS-concurrent-preclean:0.018/0.019secs] [Times: user=0.02sys=0.00, real=0.02secs]
2013-11-27T04:00:19.250+0800:38899.175: [CMS-concurrent-abortable-preclean-start]
CMS: abort preclean due to time2013-11-27T04:00:25.252+0800:38905.176: [CMS-concurrent-abortable-preclean:5.993/6.002secs] [Times: user=6.97sys=2.16, real=6.00secs]
2013-11-27T04:00:25.253+0800:38905.177: [GC[YG occupancy:573705K (1877376K)]38905.177: [Rescan (parallel) ,0.3685690secs]38905.546: [weak refs processing,0.0024100secs]38905.548: [cla
ss unloading,0.0177600secs]38905.566: [scrub symbol & string tables,0.0154090secs] [1CMS-remark: 1547313K(2146304K)] 2121018K(4023680K),0.4229380secs] [Times: user=1.41sys=0.01, real=
0.43secs]
2013-11-27T04:00:25.676+0800:38905.601: [CMS-concurrent-sweep-start]
2013-11-27T04:00:26.436+0800:38906.360: [CMS-concurrent-sweep:0.759/0.760secs] [Times: user=1.06sys=0.48, real=0.76secs]
2013-11-27T04:00:26.436+0800:38906.360: [CMS-concurrent-reset-start]
2013-11-27T04:00:26.441+0800:38906.365: [CMS-concurrent-reset:0.005/0.005secs] [Times: user=0.00sys=0.00, real=0.00secs]

这个是一个正常的CMS的日志,共分为七个步骤,重点关注initial-mark和remark这两个阶段,因为这两个是停机的。

A、[GC [1 CMS-initial-mark: 1547313K(2146304K)] 1734957K(4023680K), 0.1390860 secs] [Times: user=0.14 sys=0.00, real=0.14 secs]

各个数据依次表示标记前后old区的所有对象占内存大小和old的capacity,整个JavaHeap(不包括perm)所有对象占内存总的大小和JavaHeap的capacity。

B、2013-11-27T04:00:25.253+0800: 38905.177: [GC[YG occupancy: 573705 K (1877376 K)]38905.177: [Rescan (parallel) , 0.3685690 secs]38905.546: [weak refs processing, 0.0024100 secs]38905.548: [class unloading, 0.0177600 secs]38905.566: [scrub symbol & string tables, 0.0154090 secs] [1 CMS-remark: 1547313K(2146304K)] 2121018K(4023680K), 0.4229380 secs] [Times: user=1.41 sys=0.01, real=0.43 secs]

Rescan (parallel)表示的是多线程处理young区和多线程扫描old+perm的卡表的总时间, parallel 表示多GC线程并行。

weak refs processing 处理old区的弱引用的总时间,用于回收native memory。

class unloading 回收SystemDictionary消耗的总时间

a)首先jvm根据-XX:CMSInitiatingOccupancyFraction,-XX:+UseCMSInitiatingOccupancyOnly     来决定什么时间开始垃圾收集
b)如果设置了-XX:+UseCMSInitiatingOccupancyOnly,那么只有当old代占用确实达到了     -XX:CMSInitiatingOccupancyFraction参数所设定的比例时才会触发cms gc
c)如果没有设置-XX:+UseCMSInitiatingOccupancyOnly,那么系统会根据统计数据自行决定什么时候    触发cms gc;因此有时会遇到设置了80%比例才cms gc,但是50%时就已经触发了,就是因为这个参数    没有设置的原因
d)当cms gc开始时,首先的阶段是CMS-initial-mark,此阶段是初始标记阶段,是stop the world阶段,     因此此阶段标记的对象只是从root集最直接可达的对象
CMS-initial-mark:961330K(1572864K),指标记时,old代的已用空间和总空间
e)下一个阶段是CMS-concurrent-mark,此阶段是和应用线程并发执行的,所谓并发收集器指的就是这个,     主要作用是标记可达的对象
此阶段会打印2条日志:CMS-concurrent-mark-start,CMS-concurrent-mark
f)下一个阶段是CMS-concurrent-preclean,此阶段主要是进行一些预清理,因为标记和应用线程是并发执行的,    因此会有些对象的状态在标记后会改变,此阶段正是解决这个问题
    因为之后的Rescan阶段也会stop the world,为了使暂停的时间尽可能的小,也需要preclean阶段先做一部分    工作以节省时间
此阶段会打印2条日志:CMS-concurrent-preclean-start,CMS-concurrent-preclean
g)下一阶段是CMS-concurrent-abortable-preclean阶段,加入此阶段的目的是使cms gc更加可控一些,     作用也是执行一些预清理,以减少Rescan阶段造成应用暂停的时间
此阶段涉及几个参数:
-XX:CMSMaxAbortablePrecleanTime:当abortable-preclean阶段执行达到这个时间时才会结束
-XX:CMSScheduleRemarkEdenSizeThreshold(默认2m):控制abortable-preclean阶段什么时候开始执行,即当eden使用达到此值时,才会开始abortable-preclean阶段
-XX:CMSScheduleRemarkEdenPenetratio(默认50%):控制abortable-preclean阶段什么时候结束执行
此阶段会打印一些日志如下:
CMS-concurrent-abortable-preclean-start,CMS-concurrent-abortable-preclean,CMS:abort preclean due to time XXX
h)再下一个阶段是第二个stop the world阶段了,即Remark阶段,此阶段暂停应用线程,对对象进行重新扫描并     标记
YG occupancy:964861K(2403008K),指执行时young代的情况
CMS remark:961330K(1572864K),指执行时old代的情况
此外,还打印出了弱引用处理、类卸载等过程的耗时
i)再下一个阶段是CMS-concurrent-sweep,进行并发的垃圾清理
j)最后是CMS-concurrent-reset,为下一次cms gc重置相关数据结构

 

一些日志:

 

、promotion failed的一段日志

1
2
2013-11-27T03:00:53.638+0800:35333.562: [GC35333.562: [ParNew (promotion failed): 1877376K->1877376K(1877376K),15.7989680secs]35349.361: [CMS: 2144171K->2129287K(2146304K),10.4200280sec
s] 3514052K->2129287K(4023680K), [CMS Perm : 119979K->118652K(190132K)],26.2193500secs] [Times: user=30.35sys=5.19, real=26.22secs]

解释如下:

1
2
3
4
5
1877376K->1877376K(1877376K),15.7989680secs young区
2144171K->2129287K(2146304K),10.4200280sec old区情况
3514052K->2129287K(4023680K) heap区情况
119979K->118652K(190132K)],26.2193500secs perm区情况
[Times: user=30.35sys=5.19, real=26.22secs] 整个过程的时间消耗

一段正常的Young GC的日志

1
2
2013-11-27T04:00:07.345+0800:38887.270: [GC38887.270: [ParNew: 1791076K->170624K(1877376K),0.2324440secs] 2988366K->1413629K(4023680K),0.2326470secs] [Times: user=0.80sys=0.00, real=0.
23secs]

ParNew这个表明是并行的回收方式,具体的分别是young区、整个heap区的情况;

 

 

一段通过system.gc产生的FullGC日志

1
2013-07-21T17:44:01.554+0800:50.568: [Full GC (System)50.568: [CMS: 943772K->220K(2596864K),2.3424070secs] 1477000K->220K(4061184K), [CMS Perm : 3361K->3361K(98304K)],2.3425410secs] [Times: user=2.33sys=0.01, real=2.34secs]

解释如下:

Full GC (System)意味着这是个system.gc调用产生的MSC。

“943772K->220K(2596864K), 2.3424070 secs”表示:这次MSC前后old区内总对象大小,old的capacity及这次MSC耗时。

“1477000K->220K(4061184K)”表示:这次MSC前后JavaHeap内总对象大小,JavaHeap的capacity。

“3361K->3361K(98304K)], 2.3425410 secs”表示:这次MSC前后Perm区内总对象大小,Perm区的capacity。

一个特殊的GC日志,根据动态计算直接进行的FullGC(CMS的方式)悲观策略

1
2013-03-13T13:48:06.349+0800:7.092: [GC7.092: [ParNew: 471872K->471872K(471872K),0.0000420secs]7.092: [CMS: 366666K->524287K(524288K),27.0023450secs] 838538K->829914K(996160K), [CMS Perm : 3196K->3195K(131072K)],27.0025170secs]

ParNew的时间特别短,jvm在minor gc前会首先确认old是不是足够大,如果不够大,这次young gc直接返回,进行MSC。

 

 

参考文档:

http://hi.baidu.com/576699909/item/9daf4d7c33a09f316f29f66c

http://www.viluo.com/post/25

http://blog.csdn.net/fycghy0803/article/details/6679827

http://www.cnblogs.com/redcreen/archive/2011/05/04/2037029.html

http://www.cnblogs.com/redcreen/archive/2011/05/04/2037057.html#CMSInitiatingOccupancyFraction_value

http://blog.csdn.net/jiasanshou/article/details/24908567

http://blog.csdn.net/jiasanshou/article/details/24908371

http://blog.csdn.net/jiasanshou/article/details/24908187

http://blog.csdn.net/calvinxiu/article/details/1614473

http://guoliangqi.iteye.com/blog/630692

http://www.blogjava.net/BlueDavy/archive/2009/10/09/297562.html

http://www.iteye.com/topic/473874

http://hllvm.group.iteye.com/group/wiki/2870-JVM

http://ee.riaos.com/?p=20009582

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值