您好,我是湘王,这是我的CSDN博客,欢迎您来,欢迎您再来~
上回说到了年轻代、老年代与数据计算的一个案例。接下来就先讲一讲年轻代和老年代的两个垃圾回收器:ParNew和CMS。
和Serial垃圾回收器一样,ParNew也是对年轻代进行垃圾回收,但Serial垃圾回收器是单线程的,而ParNew是多线程的。
如果要指定使用ParNew垃圾回收器,只需在JVM中开启-XX:+UseParNewGC参数就行了。
ParNew的默认线程数量和CPU核数一致,如果要调节ParNew线程数量,使用-XX:ParallelGCThreads参数即可,一般不用更改。启动系统时可以分别指定客户端模式与服务器模式,只要在参数后分别跟上-client和-server就行了。客户端模式与服务器模式的区别是:
1、如果部署在类Unix机器上,就用服务器模式;如果部署在Windows上,就是客户端模式;
2、服务器模式通常是多核的大型系统后端;客户端模式通常是给applet这类的客户端程序使用,机器也多为单核。
而老年代的垃圾回收器CMS使用的是标记清理算法:标记出哪些是垃圾对象,然后就把它们清理掉。但这可能会造成内存碎片过多,导致空间严重浪费。因此,CMS采取GC线程 + 工作线程同时执行的方式。
CMS的一次典型垃圾回收过程如下:
1、初始标记阶段。初始标记意味着系统要进入Stop the World状态,仅仅标记出所有被GC Roots直接引用的对象(方法的局部变量和静态变量),并不会执行清理。
2、并发标记阶段。系统程序恢复运行,GC线程则会尽可能对全部老年代里已有对象进行GC Roots追踪(即弄清全部老年代里的对象是否被引用了)。
3、重新标记阶段。系统程序再次被禁止运行,对在上一阶段程序运行时状态发生变更的对象进行标记。
4、最后是并发清理阶段。系统程序再次恢复运行,清理掉之前标记为垃圾的对象即可。
因此这就可以解释为什么老年代会比年轻代速度慢很多了。主要就是CMS会导致资源紧张。系统线程和GC线程同时工作,会导致有限的CPU资源被占用,尤其是进行GC Roots时。
CMS默认启动的GC线程数量 = (CPU核数 + 3) / 4。
在第四阶段(即并发清理阶段),会产生浮动垃圾,即GC本身产生的新垃圾。
浮动垃圾要等到下一次Minor GC时才会被回收。所以JVM会预留一些空间,保证在CMS期间让存活对象能进入老年代。前面也说过CMS的触发时机就是当老年代内存占用达到一定比例时启动。-XX:CMSInitiatingOccupancyFraction参数就是用来设置这个阈值,超过设定值则开启CMS(JDK1.8已不建议使用)。JDK8的默认比例值是92%,具体原因可以参考这个官方链接:https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/cms.html
另外,CMS执行时会遇到一个Concurrent Mode Failure问题:也就是当要进入到老年代的存活对象大于可用内存空间时,意味着CMS执行失败。此时会自动用Serial Old代替CMS——强行Stop the World,垃圾回收完成后再恢复系统线程。
执行GC时产生的内存碎片由这两个JVM参数决定:
1、-XX:UseCMSCompactAtFullCollection参数会默认打开,避免CMS产生的内存碎片问题,意思是Full GC之后再次进行Stop the World,清理碎片(JDK1.8已不建议使用);
2、-XX:CMSFullGCsBeforeCompaction参数表示执行多少次Full GC之后再进行碎片清理(默认0,表示每次都清理,JDK1.8已不建议使用)。
感谢您的大驾光临!咨询技术、产品、运营和管理相关问题,请关注后留言。欢迎骚扰,不胜荣幸~