JVM如果不指定-server或-client选项,JVM会在启动的时候根据硬件环境判断以server模式启动还是以client模式启动(适用于Java 5及以上版本)。
JVM工作在server模式可以大大提高性能,但应用的启动会比client模式慢大概10%。当该参数不指定时,虚拟机启动检测主机是否为服务器,如果是,则以server模式启动,否则以client模式启动,Java 5检测的根据是至少2个CPU和最低2GB内存。
当JVM用于启动GUI界面的交互应用时适合于使用client模式,当JVM用于运行服务器后台程序时建议用server模式。
JVM 堆内存:Java堆由Perm区和Heap区组成,Heap区由Old区和New区(也叫Young区)组成,New区由Eden区、From区和To区(Survivor)组成,可以参考图3。
Eden区用于存放新生成的对象。Eden中的对象生命不会超过一次Minor GC。
New区的几种Collector
<!--[if !supportLists]-->1、 <!--[endif]-->串行GC(Serial Copying)
client模式下的默认GC方式,也可使用-XX:+UseSerialGC指定。
<!--[if !supportLists]-->2、 <!--[endif]-->并行回收GC(Parallel Scavenge)
server模式下的默认GC方式,也可用-XX:+UseParallelGC强制指定。
采用PS时,默认情况下JVM会在运行时动态调整Eden:S0:S1的比例,如果不希望自动调整可以使用-XX:-UseAdaptiveSizePolicy参数,内存分配和回收的算法和串行相同,唯一不同仅在于回收时为多线程。
<!--[if !supportLists]-->3、 <!--[endif]-->并行GC(ParNew)
CMS GC时默认采用,也可以采用-XX:+UseParNewGC指定。
内存分配、回收和PS相同,不同的仅在于会收拾会配合CMS做些处理。
Old区的几种Collector
<!--[if !supportLists]-->1、 <!--[endif]-->串行GC(Serial MSC)
client模式下的默认GC方式,可通过-XX:+UseSerialGC强制指定。每次进行全部回收,进行Compact,非常耗费时间。
<!--[if !supportLists]-->2、 <!--[endif]-->并行GC(Parallel MSC)
server模式下的默认GC方式,也可用-XX:+UseParallelGC=强制指定。可以在选项后加等号来制定并行的线程数。
<!--[if !supportLists]-->3、 <!--[endif]-->并发GC(CMS)线上环境采用的GC方式,也就是Realese环境的方式
使用CMS是为了减少GC执行时的停顿时间,垃圾回收线程和应用线程同时执行,可以使用-XX:+UseConcMarkSweepGC=指定使用,后边接等号指定并发线程数。CMS每次回收只停顿很短的时间,分别在开始的时候(Initial Marking),和中间(Final Marking)的时候,第二次时间略长。具体CMS的过程可以参考相关文档。JStat中将Initial Mark和Remark都统计成了FGC。
CMS一个比较大的问题是碎片和浮动垃圾问题(Floating Gabage)。碎片是由于CMS默认不对内存进行Compact所致,可以通过-XX:+UseCMSCompactAtFullCollection。
总体来讲,Old区的大小较大,垃圾回收算法较费时间,导致较长时间的应用线程停止工作,而且需要进行Compact,所以不应该出现较多Major GC。Major GC的时间常常是Minor GC的几十倍。JVM内存调优的重点,减少Major GC 的次数,因为为Major GC 会暂停程序比较长的时间,如果Major GC 的次数比较多,意味着应用程序的JVM内存参数需要进行调整。
此处可以结合后边的Major GC部分理解。
图1
Minor GC:也称为YGC(Young Generation Collection)针对于New区进行的垃圾回收。当New区满了的时候,进行YGC。默认情况下Full GC会触发Minor GC,在PS GC时可通过设置-XX:-ScavengeBeforeFullGC来禁止触发Minor GC。
Major GC:有的时候就是指Full Collection(http://www.ibm.com/developerworks/java/library/j-jtp11253/中说:A major collection will collect both the young and old generation. 这篇文章是针对JDK1.4的)。Full Collection的时候所有的内存区都进行垃圾回收,耗费的时间比较长。系统在垃圾回收时会有明显的暂停,而且暂停时间会因为堆越大而越长。正常情况不应该出现大量的FGC。一般,GC首先使用New区特有的回收算法对New区进行回收,也就是YGC。然后,GC使用Old区的回收算法对Old区和Perm区进行回收。如果需要进行Compact,各个区分开进行压缩。
有的时候,FGC并非这样。比如,YGC后有部分对象需要放入Survivor区,但是Survivor区不够大,这些对象就要直接放入Old区,这时由于之前已经执行了一次YGC,所以此次不进行YGC,而是直接使用Old区的回收算法对整个Heap进行Major GC。但是如果使用的是CMS Collector,则必须要执行YGC,因为CMS无法回收New区。
CMS使用独立的GC线程与应用线程并发执行,目的是在Old区满之前完成内存的回收。正常的情况下,CMS能够在不影响应用线程的情况下完成工作。所以进行GC的时候,应用线程几乎不会停止工作。但是,如果CMS不能够在Old区变满之前完成自己的工作,这个时候JVM会将所有的应用线程停止以完成垃圾回收(此种情况为Concurrent Mode Failure,发生FGC还有另外一种情况:Promotion Failed)。Promotion Failed一种情况发生在Old区存在大量碎片的情况下,Old区无法容纳下New区转移过来的对象,此时Old区可能并没有满;另外一种情况发生在Old区快满了,无法容纳新对象。在所有应用线程停止时进行的GC被称为FGC。
从图1中可以看出,FGC collector只能为串行GC或并行GC而不能为CMS。
Old区GC的机制为:GC首先会将Old区分块,然后针对每个块中的内存标记为使用和未使用两种状态。GC将使用率比较高的内存在Compact的时候忽略,而只关注那些使用率比较低的内存。标记后,GC将进行Compact操作,也就是碎片整理,如图2。
图2
从New区到Old区的Promotion主要有三种情况:
一、在经历了多次的 Minor GC 后仍然存活在触发了Minor GC后,存活对象被存入Survivor区在经历了多次Minor GC之后,如果仍然存活的话,则该对象被晋升到Old区。
From和To作用是相同的。每次Minor GC发生的时候,未被回收的对象从From被Copy入To或者从To被Copy入From。图3为一次Minor GC发生后的样子。当第二次Minor GC发生的时候,From被清空,From中的存活对象被复制进To区。
之所以要这样做的原因是:
<!--[if !supportLists]-->1、 <!--[endif]-->为了整理内存,如图4所示。如果不进行内存整理的话,Survivor区也会存在碎片问题。
<!--[if !supportLists]-->2、 <!--[endif]-->再有就是基于这样一个假设:“大部分对象都是短命的”。理想情况是,在有限的几次Minor GC后所有的资源都会被回收。
图3
图4
二、 Minor GC 后 Survivor 空间不足就直接放入 Old 区 三、大对象、大数组,直接在 Old 区中分配具体多大的对象由-XX:PretenureSizeThreshold=来设置此参数在PS GC时无效,PS GC会动态计算这个参数。
如果想看GC 的确切情况最好不用JStat 而是加上如下JVM 参数-verbose:gc -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:./gc.log 。如下,这里可以看到因为Concurrent Mode Failure引起的FGC,这也说明了Full Collection和CMS的不同:
344700.914: [Full GC 344700.915: [CMS (concurrent mode failure):
3103167K->3103167K(3103168K), 20.3730500 secs] 3145663K-
>3108336K(3145664K), [CMS Perm : 140469K->140454K(236664K)],
20.3734200 secs]
344721.314: [GC [1 CMS-initial-mark: 3103167K(3103168K)]
3108391K(3145664K), 0.0361640 secs]
344721.350: [CMS-concurrent-mark-start]
参考文档:
1 http://www.oracle.com/technetwork/java/gc-tuning-5-138395.html#0.0.0.%20The%20Concurrent%20Low%20Pause%20Collector%7Coutline --- 《Tuning Garbage Collection with the 5.0 Java Virtual Machine》
2、《JVM调优总结》
3、https://java.sun.com/j2se/reference/...whitepaper.pdf
4、http://blogs.sun.com/jonthecollector/entry/when_the_sum_of_the
5、http://blogs.sun.com/jonthecollector/entry/what_the_heck_s_a
6、Sun Hotspot 1.6 JVM GC(Garbage Collector)http://bluedavy.com
来自IBM的一组统计数据:
98%的java对象,在创建之后不久就变成了非活动对象;只有2%的对象,会在长时间一直处于活动状态。
如果能对这两种对象区分对象,那么会提交GC的效率。在sun jdk gc中(具体的说,是在jdk1.4之后的版本),提出了不同生命周期的GC策略。
young generation:
生命周期很短的对象,归为young generation。由于生命周期很短,这部分对象在gc的时候,很大部分的对象已经成为非活动对象。因此针对young generation的对象,采用copy算法,只需要将少量的存活下来的对象copy到to space。存活的对象数量越少,那么copy算法的效率越高。
young generation的gc称为minor gc。经过数次minor gc,依旧存活的对象,将被移出young generation,移到tenured generation
young generation分为:
eden:每当对象创建的时候,总是被分配在这个区域
survivor1:copy算法中的from space
survivor2:copy算法中的to sapce (备注:其中survivor1和survivor2的身份在每次minor gc后被互换)
minor gc的时候,会把eden+survivor1(2)的对象copy到survivor2(1)去。
tenured generation:
生命周期较常的对象,归入到tenured generation。一般是经过多次minor gc,还 依旧存活的对象,将移入到tenured generation。(当然,在minor gc中如果存活的对象的超过survivor的容量,放不下的对象会直接移入到tenured generation)
tenured generation的gc称为major gc,就是通常说的full gc。
采用compactiion算法。由于tenured generaion区域比较大,而且通常对象生命周期都比较长,compaction需要一定时间。所以这部分的gc时间比较长。
minor gc可能引发full gc。当eden+from space的空间大于tenured generation区的剩余空间时,会引发full gc。这是悲观算法,要确保eden+from space的对象如果都存活,必须有足够的tenured generation空间存放这些对象。
Permanet Generation:
该区域比较稳定,主要用于存放classloader信息,比如类信息和method信息。
对于spring hibernate这些需要动态类型支持的框架,这个区域需要足够的空间。
这部分内容相对比较理论,可以结合jstat,jmap等命令(当然也可以使用jconsole,jprofile,gciewer等工具),观察jdk gc的情