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
常用组合套路
一般而言,这垃圾收集器也是有固定的几个套路,大概四个:
- -XX:+UseSerialGC(young Copy and old MarkSweepCompact),新生代与老年代都是单线程收集,适合机器比较烂,client的默认模式。
- -XX:+UseParallelGC -XX:+UseParallelOldGC(young PS Scavenge old PS MarkSweep),新生代与老年代都是并发的方式,适合机器相对好,吞吐量优先,对等待时间不敏感,如大规模计算,server的默认模式
- -XX:+UseConcMarkSweepGC -XX:+UseParNewGC(young ParNew old ConcurrentMarkSweep),最小等待时间优先,适合机器好,用户响应时间低的场景,一般互联网公司,B/S模式使用。
- -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
老年代和新生代的比例
-XX:NewRatio=value
默认情况下,这个数值是 2,意味着老年代是新生代的 2 倍大;换句话说,新生代是堆大小的 1/3。
为什么堆只分配了1/4内存,其他系统内存做什么用?
除了堆内存,还有很多需要使用内存的:
- 永久代内存,除了堆外的第二大内存开销
- Direct Memory,当NIO较多的时候,需要注意
- 线程堆栈,每个线程1M,线程过多可能会有内存不够用情况
- Socket缓存,每个Socket都有Receive和Send两个缓冲区,分别占用大约37KB与25KB
- JNI代码,如果调用本地库,本地库的代码不在堆中
- 虚拟机、GC线程、操作系统本身等