Java 虚拟机(二)细节分析

Minor GC和Full GC的触发时机?

Minor GC一般是在无法分配对象的时候触发。

CMS收集器在内存使用92%后出发。

java8里,使用CMS收集器,触发老年代gc除了超过阀值外,还有一种可能,那就是年轻代gc后无法释放空间,需要老年代来担保分配,但老年代也没有足够的空间来担保,就会导致老年代gc。

默认的垃圾收集器?

java8,使用jconsole查看,在VM概要中看到的为:

垃圾收集器:  名称 = ‘Copy’, 收集 = 82, 总花费时间 = 0.129 秒
垃圾收集器:  名称 = ‘MarkSweepCompact’, 收集 = 0, 总花费时间 = 0.000 秒

新生代与老年代使用的应该都是Serial系列的,分别是Serial和SerialOld垃圾收集器。参考:Oracle JVM Garbage Collectors Available From JDK 1.7.0_04 And After,文中也说明了如何进行设置。

Young generation collectors
Copy (enabled with -XX:+UseSerialGC)
PS Scavenge (enabled with -XX:+UseParallelGC)
ParNew (enabled with -XX:+UseParNewGC)
G1 Young Generation (enabled with -XX:+UseG1GC)

Old generation collectors
MarkSweepCompact (enabled with -XX:+UseSerialGC)
PS MarkSweep (enabled with -XX:+UseParallelOldGC)
ConcurrentMarkSweep (enabled with -XX:+UseConcMarkSweepGC)
G1 Mixed Generation (enabled with -XX:+UseG1GC)

上述运行的为前台运行,启动的时候VM参数啥也没有增加,在VM启动参数加上“-client”后的使用的仍然是上述两个垃圾回收器。启动的时候如果加上”-server”,再次用jconsole查看,垃圾收集器为’PS MarkSweep’和’PS Scavenge’,也就是Parallel Scavenge和Parallel Old垃圾回收器。

上述测试也表明了在jdk1.8下在client和server模式下的默认的垃圾收集器,分别是Serial的和Paralle的,client模式下,假设client端的机器不咋样,使用高效的Serial就可以了。而Server端,做的假设是一般用户是还是考虑“性价比”,追求高的吞吐量,尽量发挥硬件的性能,比较时候对延迟不敏感,特别是大量计算的场景。

而实际中,特别是互联网公司,一般是Parallel New + CMS的组合,目的是为了提高用户的体验,最短停顿优先。在ES5.6版本中默认也是用的CMS收集器。

这G1的垃圾回收器在1.7.0_04之后被正式的支持了,也有很多公司在用,比如阿里。对于这玩意调优可以参考,Tips for Tuning the Garbage First Garbage Collector

常用组合套路

一般而言,这垃圾收集器也是有固定的几个套路,大概四个:

  1. -XX:+UseSerialGC(young Copy and old MarkSweepCompact),新生代与老年代都是单线程收集,适合机器比较烂,client的默认模式。
  2. -XX:+UseParallelGC -XX:+UseParallelOldGC(young PS Scavenge old PS MarkSweep),新生代与老年代都是并发的方式,适合机器相对好,吞吐量优先,对等待时间不敏感,如大规模计算,server的默认模式
  3. -XX:+UseConcMarkSweepGC -XX:+UseParNewGC(young ParNew old ConcurrentMarkSweep),最小等待时间优先,适合机器好,用户响应时间低的场景,一般互联网公司,B/S模式使用。
  4. -XX:+UseG1GC(young G1 Young and old G1 Mixed),将来替代第三个模式的。

其他的虽然有很多可以组合的,但是很多不常用,而且有的deprecated in Java 8 and removed in Java 9。上述是主流的四个方式。

为什么大对象直接进入老年代?

在新生代,一般是复制算法,但是,复制有个弊端,就是对大块内存拷贝相对还是比较慢的,虽然在内存级别1s能有1Gb以上的速度,但是当现在的堆都很大,如果复制个10G的内存,还是需要上秒的。

另外,新生代中默认的Eden与Survivor区域的比值是8:1也就是说Survivor区域的空间是比较小的,如果拷贝过来大内存,可能Survivor是空间不够的,需要老年代进行担保,担保失败又会进入一套复杂的流程,不如直接放入老年代。

CMS收集器的场景?

上面虽然也反复提到过,是对回收停顿时间要求短的场景,但是还有个前提,CMS是对CPU敏感的,那么就要求应用程序对CPU的要求不高,不是CPU密集型的,而且CPU尽量要多于2核,CMS才可能又相对的优势。

几个关键的默认参数

-Xms -Xmx,Java8 takes Larger of 1/6th of your physical memory for your Xmssize (Minimum HeapSize) and Smaller of 1/4th of your physical memory for your -Xmxsize (Maximum HeapSize).这两个参数Xms和Xmx最好设置成一样大小,防止jvm内存调整导致的不稳定。 -Xss,设置单个线程栈的大小,JDK5.0以后每个线程堆栈大小为1M。

老年代和新生代的比例

-XX:NewRatio=value

默认情况下,这个数值是 2,意味着老年代是新生代的 2 倍大;换句话说,新生代是堆大小的 1/3。

为什么堆只分配了1/4内存,其他系统内存做什么用?

除了堆内存,还有很多需要使用内存的:

Table of Contents