JVM垃圾收集器
jvm参数总结
通用参数:
- -Xms1024m 初始化堆大小(此处的堆泛指jvm内存区域)
- -Xmx1024m 最大堆大小
- -Xmn512m 新生代大小
- -Xss256K 每个线程的堆栈大小
- -XX:SurvivorRatio=8 新生代中Eden和Survivor的比值
- -XX:PermSize=256M 永久代初始内存大小
- -XX:MaxPermSize=512M 永久代最大值
注:Java8没有永久代说法,它们被称为元空间,-XX:MetaspaceSize=N
Serial:
- -XX:UseSerialGC:强制使用Serial/Serial Old GC组合
Parnew:
- -XX:UseParnewGC:强制使用Parnew/Serial Old GC组合
- -XX:ParallelGCThreads:指定ParNew GC线程的数量,默认与CPU核数相同
Parallel:
- -XX:+UseParallelOldGC:使用Parallel/Parallel Old GC组合
- -XX:GCTimeRatio:直接设置吞吐量大小,假设设为19,则允许的最大GC时间占总时间的1/(1 +19),默认值为99,即1/(1+99)
- -XX:MaxGCPauseMillis:最大GC停顿时间,该参数并非越小越好
- -XX:+UseAdaptiveSizePolicy:开启该参数,-Xmn/-XX:SurvivorRatio/-XX:PretenureSizeThreshold这些参数就不起作用了,虚拟机会自动收集监控信息,动态调整这些参数以提供最合适的的停顿时间或者最大的吞吐量(GC自适应调节策略),而我们需要设置的就是-Xmx,-XX:+UseParallelOldGC或-XX:GCTimeRatio两个参数就好(当然-Xms也指定上与-Xmx相同就好)
CMS:
- -XX:+UseConcMarkSweepGC:使用ParNew/CMS GC组合
- -XX:CMSInitiatingOccupancyFraction:指定当年老代空间满了多少后进行垃圾回收
- -XX:+UseCMSCompactAtFullCollection:(默认是开启的)在CMS收集器顶不住要进行FullGC时开启内存碎片整理过程,该过程需要STW
- -XX:CMSFullGCsBeforeCompaction:指定多少次FullGC后进行碎片整理
- -XX:ParallelCMSThreads:指定CMS回收线程的数量,默认为:(CPU数量+3)/4
GC分类
-
Minor GC(年轻代gc)
触发条件:新生代采用空闲指针的方式来控制GC触发,指针保持最后一个分配的对象在新生代区间的位置,当有新的对象要分配内存时,用于检查空间是否足够,不够就触发GC。 -
Major GC/Full GC(老年代gc)
触发条件:
- system.gc()方法的调用
- Minor gc时老年代空间不足:Minor gc时候熬过一定年龄的对象会进入老年代,如果suvivor空间不住,对象进入老年代,这个时候如果老年代空间不足,会引起full gc
- 堆中分配很大的对象:堆中分配大对象的时候直接进入老年代,这个时候如果老年代空间不足也会进行
full gc - 分配担保失败:Minor gc时统计得到的Minor GC晋升到旧生代的平均大小大于老年代的剩余空间
垃圾收集器
七种垃圾收集器:
- Serial(串行GC)-复制
- ParNew(并行GC)-复制
- Parallel Scavenge(并行回收GC)-复制
- Serial Old(MSC)(串行GC)-标记-整理
- CMS(并发GC)-标记-清除
- Parallel Old(并行GC)--标记-整理
- G1(JDK1.7update14才可以正式商用)
概况图:
常用组合:
- Serial/Serial Old
- ParNew/Serial Old:与上边相比,只是比年轻代多了多线程垃圾回收而已
- Parallel Scavenge/Parallel Old:自动管理的组合
- ParNew/CMS:当下比较高效的组合
- G1:最先进的收集器,但是需要JDK1.7update14以上
Serial/Serial Old
特点:
- 年轻代Serial收集器采用单个GC线程实现"复制"算法(包括扫描、复制)
- 年老代Serial Old收集器采用单个GC线程实现"标记-整理"算法
- Serial与Serial Old都会暂停所有用户线程(即STW)
说明:
STW(stop the world):编译代码时为每一个方法注入safepoint(方法中循环结束的点、方法执行结束的点),在暂停应用时,需要等待所有的用户线程进入safepoint,之后暂停所有线程,然后进行垃圾回收。
适用场合:
- CPU核数<2,物理内存<2G的机器(简单来讲,单CPU,新生代空间较小且对STW时间要求不高的情况下使用)
- -XX:UseSerialGC:强制使用该GC组合
- -XX:PrintGCApplicationStoppedTime:查看STW时间
- 由于它实现相对简单,没有线程相关的额外开销(主要指线程切换与同步),因此非常适合运行于客户端PC的小型应用程序,或者桌面应用程序(比如swing编写的用户界面程序),以及我们平时的开发、调试、测试等。
ParNew/Serial Old
说明:
ParNew除了采用多GC线程来实现复制算法以外,其他都与Serial一样,但是此组合中的Serial Old又是一个单GC线程,所以该组合是一个比较尴尬的组合,在单CPU情况下没有Serial/Serial Old速度快(因为ParNew多线程需要切换),在多CPU情况下又没有之后的三种组合快(因为Serial Old是单GC线程),所以使用其实不多。
-XX:ParallelGCThreads:指定ParNew GC线程的数量,默认与CPU核数相同,该参数在于CMS GC组合时,也可能会用到
Parallel/Parallel Old
特点:
- 年轻代Parallel Scavenge收集器采用多个GC线程实现"复制"算法(包括扫描、复制)
- 年老代Parallel Old收集器采用多个GC线程实现"标记-整理"算法
- Parallel Scavenge与Parallel Old都会暂停所有用户线程(即STW)
说明:
- cpu利用率:CPU运行代码时间/(CPU运行代码时间+GC时间)
- CMS主要注重STW的缩短(该时间越短,用户体验越好,所以主要用于处理很多的交互任务的情况)
- Parallel Scavenge/Parallel Old主要注重吞吐量(吞吐量越大,说明CPU利用率越高,所以主要用于处理很多的CPU计算任务而用户交互任务较少的情况)
参数设置:
- -XX:+UseParallelOldGC:使用该GC组合
- -XX:GCTimeRatio:直接设置吞吐量大小,假设设为19,则允许的最大GC时间占总时间的1/(1 +19),默认值为99,即1/(1+99)
- -XX:MaxGCPauseMillis:最大GC停顿时间,该参数并非越小越好
- -XX:+UseAdaptiveSizePolicy:开启该参数,-Xmn/-XX:SurvivorRatio/-XX:PretenureSizeThreshold这些参数就不起作用了,虚拟机会自动收集监控信息,动态调整这些参数以提供最合适的的停顿时间或者最大的吞吐量(GC自适应调节策略),而我们需要设置的就是-Xmx,-XX:+UseParallelOldGC或-XX:GCTimeRatio两个参数就好(当然-Xms也指定上与-Xmx相同就好)
适用场合:
- 很多的CPU计算任务而用户交互任务较少的情况
- 不想自己去过多的关注GC参数,想让虚拟机自己进行调优工作
- 对吞吐量要求较高,或需要达到一定的量。
ParNew/CMS
说明:
以上只是年老代CMS收集的过程,CMS是多回收线程的,不要被上图误导,默认的线程数:(CPU数量+3)/4
CMS主要注重STW的缩短(该时间越短,用户体验越好,所以主要用于处理很多的交互任务的情况)
特点:
- 年轻代ParNew收集器采用多个GC线程实现"复制"算法(包括扫描、复制)
- 年老代CMS收集器采用多线程实现"标记-清除"算法
- 初始标记:标记与根集合节点(GC ROOTS)直接关联的节点。时间非常短,需要STW
- 并发标记:遍历之前标记到的关联节点,继续向下标记所有存活节点。时间较长。
- 重新标记:重新遍历trace并发期间修改过的引用关系对象。时间介于初始标记与并发标记之间,通常不会很长。需要STW
- 并发清理:直接清除非存活对象,清理之后,将该线程占用的CPU切换给用户线程
- 初始标记与重新标记都会暂停所有用户线程(即STW),但是时间较短;并发标记与并发清理时间较长,但是不需要STW
疑惑:
并发标记期间怎样记录发生变动的引用关系对象,在重新标记期间怎样扫描这些对象?
并发标记是gc线程与用户线程一起进行的过程,会导致活的对象死去或者死的对象活过来,但是我们在重新标记的时候如果完全将它们区分出来,就要停机全局扫描,这样CMS算法就失去了意义,权衡的做法是:在并发标记期间,凡是有新的老年代的Object被Reference指向,不管这个Reference是不是GCROOTS引申出来的,将它加入到重新标记的起点集合中去,那么在重新标记的时候就从集合中的Object出发进行trace就行了,这样STW的时间也比较短,带来的缺陷是可能已经死亡的对象没有被区分出来,但是存活的对象不会被垃圾收集,宁可放过不可错杀
缺点:
- 并发标记与并发清理:按照说明的第二点来讲,假设有2个CPU,那么其中有一个CPU会用于垃圾回收,而另一个用于用户线程,这样的话,之前是两CPU运行用户线程,现在是一个,那么效率就会急剧下降。也就是说,降低了吞吐量(即降低了CPU使用率)
- 并发清理:在这一过程中,产生的垃圾(称为浮动垃圾)无法被清理(因为发生在重新标记之后)
- 并发标记与并发清理:由于是与用户线程并发的,所以用户线程可能会分配对象进入老年代,因此CMS进行老年代GC的时候需要预留出一定空间,jdk1.5默认情况下68%就会CMSGC,jdk1.6提高到了92%,具体可以通过-XX:CMSInitiatingOccupancyFraction来指定当年老代空间满了多少后进行垃圾回收,如果CMS运行期间预留的内存不够,就会出现“Concurrent ModeFailure”失败,这个时候临时启用Serial Old方案进行gc
- 标记-清理算法:会产生内存碎片,由于是在老年代,可能会提前触发FullGC(这正是我们要尽量减少的)
参数设置:
-XX:+UseConcMarkSweepGC:使用该GC组合
-XX:CMSInitiatingOccupancyFraction:指定当年老代空间满了多少后进行垃圾回收
-XX:+UseCMSCompactAtFullCollection:(默认是开启的)在CMS收集器顶不住要进行FullGC时开启内存碎片整理过程,该过程需要STW
-XX:CMSFullGCsBeforeCompaction:指定多少次FullGC后进行碎片整理
-XX:ParallelCMSThreads:指定CMS回收线程的数量,默认为:(CPU数量+3)/4
适用场合:
用于处理很多的交互任务的情况