本文转载自公众号  匠心零度

问题现象

代码如下,使用 ParNew + Serial Old 回收器组合与使用 ParNew + CMS 回收器组合时,结果为什么差异如此之大 ?

   private static final int _1MB = 1024 * 1024;public static void main(String[] args) throws Exception {byte[] all1 = new byte[2 * _1MB];byte[] all2 = new byte[2 * _1MB];byte[] all3 = new byte[2 * _1MB];byte[] all4 = new byte[7 * _1MB];System.in.read();}

jvm参数配置如下:

  1. -Xmx20m

  2. -Xms20m

  3. -Xmn10m

  4. -XX:+UseParNewGC

  5. -XX:+UseConcMarkSweepGC

  6. -XX:+UseCMSInitiatingOccupancyOnly

  7. -XX:CMSInitiatingOccupancyFraction=75

通过jstat命令,查看结果如下:

关于jstat命令详情可以参考:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jstat.html

jvm参数调整如下:

  1. -Xmx20m

  2. -Xms20m

  3. -Xmn10m

  4. -XX:+UseParNewGC

通过jstat命令,查看结果如下:

说明

上面的题目仅仅是一个切入点而已,希望通过一个切入点把jvm的一些基础知识刚刚好说明下,顺便解答下上面的现象。

内存相关简单说明

图中参数:-Xms设置最小堆空间大小(一般建议和-Xmx一样)。-Xmx设置最大堆空间大小。-Xmn设置新生代大小。-XX:MetaspaceSize设置最小元数据空间大小。-XX:MaxMetaspaceSize设置最大元数据空间大小。-Xss设置每个线程的堆栈大小(这里有个故事,3年前用正则表达式,后续有空正则表达式再说)。

备注:tenured空间就用减法操作即可明白,堆空间大小减去年轻代大小就可以了。

说到这里,下面这个几个参数应该明白了。

  1. -Xmx20m

  2. -Xms20m

  3. -Xmn10m

备注:参数-XX:SurvivorRatio用来表示s0、s1、eden之间的比例,默认情况下-XX:SurvivorRatio=8表示 s0:s1:eden=1:1:8。

得出结论:eden=8M,s0=1M,s1=1M,tenured=10M。

JVM垃圾回收期组合

还有一个问题需要解决,jvm垃圾回收器方面,下面这个图,我是我的JVM菜鸟进阶高手之路八(一些细节),里面的,当时依稀记得这个图应该是飞哥发给我的。

由于那个时候jdk9还没有出来,可以去看看我的JVM菜鸟进阶高手之路十二(jdk9、JVM方面变化, 蹭热度),虽然有些有些稍微去掉了,但是整体的组合还是影响不大。

由于上面的2个jvm参数都是基于分代收集算法的(先不考虑G1)

  • 依据对象的存活周期进行分为新生代,老年代。

  • 根据不同代的特点,选取合适的收集算法

  • 新生代,适合复制算法

  • 老年代,适合标记清理或者标记压缩

复制算法

  • 将原有的内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收。

  • 不适用于存活对象较多的场合 如老年代。(年轻代对象基本都是朝生夕灭所以特别适合,由于那样的话复制就少,如果类似老年代有大量存活对象,那么进行复制算法性能就不是特别好了)

备注:使用复制算法的优点:每次都是对其中的一块进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况了,使用复制算法的缺点:对空间有一定浪费,所以复制空间一般不会特别大。

标记清除标记-清除算法将垃圾回收分为两个阶段:标记阶段和清除阶段。在标记阶段,首先先找出根对象,标记所有从根节点开始的可达对象。因此,未被标记的对象就是未被引用的垃圾对象。然后,在清除阶段,清除所有未被标记的对象。

备注:java根对象:

  • 虚拟机栈中引用的对象。

  • 方法区中类静态属性实体引用的对象。

  • 方法区中常量引用的对象。

  • 本地方法栈中JNI引用的对象。

  • 等等。

    标记清除算法缺点:标记清除会产生不连续的内存碎片,如果空间内存碎片过多会导致,当程序在运行过程中需要分配空间时找不到足够的连续空间而不得不提前触发一次垃圾收集动作(根据算法不一样效果也不一样)。

标记压缩标记-压缩算法适合用于存活对象较多的场合,如老年代。它在标记-清除算法的基础上做了一些优化。和标记-清除算法一样,标记-压缩算法也首先需要从根节点开始,对所有可达对象做一次标记。但之后,它并不简单的清理未标记的对象,而是将所有的存活对象压缩到内存的一端。之后,清理边界外所有的空间。

备注:这样带来的好处就是不会参数内存碎片问题了。

上面已经说明了这么多了,我们可以继续说明上题中JVM的其他参数了。

  1. -XX:+UseParNewGC

  2. -XX:+UseConcMarkSweepGC

-XX:+UseParNewGC 表示新生代使用ParNew并行收集器,-XX:+UseConcMarkSweepGC 表示老年代使用CMS回收器(CMS收集器是基于“标记-清除”算法实现的,特别提醒由于CMS是标记清除算法实现的所以是存在碎片问题的)。可以去看看我的JVM菜鸟进阶高手之路六(JVM每隔一小时执行一次Full GC)、以及JVM菜鸟进阶高手之路七(tomcat调优以及tomcat7、8性能对比)图片就取的这两篇里面的。

备注:通过jstat -gcutil pid 查看的FGC这列的时候,CMS gc通常都是+2一次的,由于CMS-initial-mark和CMS-remark会stop-the-world。

所以看到这个图的FGC应该没有什么问题了吧。

  1. -XX:+UseCMSInitiatingOccupancyOnly

  2. -XX:CMSInitiatingOccupancyFraction=75

还有这2个参数关于cms的,-XX:+UseCMSInitiatingOccupancyOnly表示JVM不基于运行时收集的数据来启动CMS垃圾收集周期通过CMSInitiatingOccupancyFraction的值进行每一次CMS收集,-XX:CMSInitiatingOccupancyFraction=75 表示当老年代的使用率达到阈值75%时会触发CMS GC。

备注:jstat -gcutil可以看出上图的老年代的使用率才60.02%

还有最后一个参数解释:

  1. -XX:+UseParNewGC

-XX:+UseParNewGC 表示新生代使用ParNew并行收集器,那么老年代呢?可以让同样参数修改代码执行一次old gc即可看日志有类似[Tenured:说明老年代使用的是Serial Old

备注:Serial Old使用的是标记压缩算法。

解题

  1. private static final int _1MB = 1024 * 1024;

  2.    public static void main(String[] args) throws Exception {

  3.        byte[] all1 = new byte[2 * _1MB];

  4.        byte[] all2 = new byte[2 * _1MB];

  5.        byte[] all3 = new byte[2 * _1MB];

  6.        byte[] all4 = new byte[7 * _1MB];

  7.        System.in.read();

  8.    }

说明:最后System.in.read();这句可以忽略,只是为了让程序阻塞在那里,不结束,这样好看日志,好看现象而已。

聪明如你一下子应该可以看到问题本质:同一份代码,jvm参数堆设置啥的都一样,年轻代gc参数也一样,唯一不同的就在于老年代gc使用上面,而jstat -gcutil图表中FGC没变的应该是正常结果,变了的CMS那个就是意外结果,所以关键点就在CMS上面了。

先来说说all1 、all12、all3、对象实例化开辟空间之后,eden空间都够,他们都在eden空间中,当all4过来的时候,eden空间不够了,需要执行ygc了。下面有2个问题需要说明,1、如果s0能存的下,可以看看JVM菜鸟进阶高手之路三:MaxTenuringThreshold新生代的对象正常情况下最多经过多少次YGC的过程会晋升到老生代(CMS情况下默认为6),说到这里可能还需要提一个参数:-XX:TargetSurvivorRatio,可以参考飞哥的:JVM Survivor行为一探究竟(http://www.jianshu.com/p/f91fde4628a5) 2、如果s0存不下,就是我们这里的情况(由于我们这里s0就是1M而已)所以直接进入到old空间了,所以可以看出来jstat -gcutil 里面的老年代的比例都是60%几了吧。ygc执行完成之后,all4就还可以在eden分配(空间够),所以可以看出来jstat -gcutil 里面的eden的比例都是89%几了吧

备注:-XX:PretenureSizeThreshold参数来设置多大的对象直接进入老年代(这个参数其实只对串行回收器和ParNew有效,对ParallelGC无效)。

如果是-Xmx20m -Xms20m -Xmn10m -XX:+UseParNewGC 这套参数,那么结果就是如图可以解释了,并且每个参数比例啥的都可以理解了。

下面来好好解释下这个现象:聪明如你一下子应该可以看到一个问题,那么就是时间间隔是每隔2s执行一次,没错就是2s执行一次。需要说道-XX:CMSWaitDuration(Time in milliseconds that CMS thread waits for young GC)默认值是2s,我们修改为-XX:CMSWaitDuration=5000看看效果:看到了吧,修改为5s就是5s执行一次变化了。那么至于为什么会执行呢??

本题就是当前新生代的对象是否能够全部顺利的晋升到老年代,如果不能,会触发CMS GC。

—————END—————

JVM菜鸟进阶高手之路相关推荐

  1. JVM 菜鸟进阶高手之路六(JVM每隔一小时执行一次Full GC)

    上次分析详细地址在:http://blog.csdn.net/lirenzuo/article/details/76257526 以为上次问题是rmi的问题就此结束了,但是问题并没有结束,其实本次问题 ...

  2. JVM菜鸟进阶高手之路十二(jdk9、JVM方面变化, 蹭热度)

    转载请注明原创出处,谢谢! 经过 4 次跳票,历经曲折的 Java 9 正式版终于发布了!今天看着到处都是jdk9发布了,新特性说明,心想这么好的蹭热度计划能错过嘛,哈哈,所以就发了这篇文章. 目前j ...

  3. JVM 菜鸟进阶高手之路十一(eden survivor分配问题)

    转载请注明原创出处,谢谢! 问题 这个Xmn设置为1G,,我用jmap -heap 看,这个Eden From To怎么不是一个整8:1:1的关系呢? 我看内存分配还是没变,我Xmn1g,感觉From ...

  4. java每隔一小时fullgc_JVM菜鸟进阶高手之路六(JVM每隔一小时执行一次Full GC)

    以为上次问题是rmi的问题就此结束了,但是问题并没有结束,其实本次问题不是rmi问题导致的,但是rmi也的确可能会有sys.gc fullgc问题. 查看GC统计汇总情况: jstat -gcutil ...

  5. 菜鸟进阶高手, 推荐 7 个 Python 上手实战项目

    作为一个语法简洁.有着丰富的第三方库的编程语言,Python 上手极为简单,短时间内就可以让你编写出能够解决实际问题的小程序,甚至去面试初级 Python 工程师的职位. 不过,如果要写出一些更复杂的 ...

  6. 大数据新手的0基础学习路线,从菜鸟到高手的成长之路

    大数据作为一个新兴的热门行业,吸引了很多人,但是对于大数据新手来说,按照什么路线去学习,才能够学习好大数据,实现从大数据菜鸟到高手的转变.这是很多想要学习大数据的朋友们想要了解的. 今天我们就来和大家 ...

  7. E71从菜鸟到高手进阶—软件安装篇 转帖

    E71从菜鸟到高手进阶-软件安装篇 1.软件安装的方法 ①手机用数据线连接电脑后用pc套件安装sisx/jar程序:(注意:JAR文件不用解压可直接安装,保证JAR的文件名为英文,否者会出错) ② 把 ...

  8. Rust语言:元编程,强大的宏系统,菜鸟到高手进阶的必经之路

    Rust语言:元编程,强大的宏系统,菜鸟到高手进阶的必经之路 编程语言的宏操作,在C和C++早期就已经存在.宏可以将重复的代码用更简短的宏函数替换,编译过程中再展开,使得代码编写的更简洁. Rust提 ...

  9. 全局变量和局部变量的区别_值得收藏!8大技巧,带你了解菜鸟和高手的区别!...

    对于Python编程者而言,或许大家都认为Python是非常简单易学的,在学习一段时间的Python之后,都认为自己的Python编程水平已经非常好了,但是python编程绝对不是简单的几句语法就可以 ...

最新文章

  1. ‘百度杯’十月场web ---login
  2. python培训就业班口碑排行榜-Python就业班培训多少钱?老男孩Python收费标准
  3. A SPI class of type org.apache.lucene.codecs.PostingsFormat with name 'Lucene40' does not exist.
  4. virtualbox安装centos7使用nat+hostonly的网络模式
  5. 反恐精英出现服务器消息,cs你已被禁用次服务器 | 手游网游页游攻略大全
  6. step4 . day4 库函数和库函数的制作
  7. 动态反射——Load,LoadFrom和LoadFile
  8. Linux 下实现普通用户只能写入某个目录
  9. c++中vecto容器r常使用的相关函数
  10. CVPR 2019 | 微软亚洲研究院 7 篇精选论文解读
  11. LeetCode 627. 交换工资
  12. laravel mysql 视图_视图入门:Laravel 支持的视图格式以及在路由中的基本使用
  13. 艾伟_转载:.NET Discovery 系列之二--string从入门到精通(勘误版下)
  14. 抽象类应用模板方法模式和接口应用之策略设计模式
  15. Word 2019怎样自定义模板,并将自定义模板设为默认模板
  16. 【JS】暑假余额倒计时
  17. 计算机二级wpsoffice知识点,2017全国计算机等级考试一级WPS office考试大纲
  18. 未转变者入侵服务器后台,未转变者(unturned)联机服务器创建方法
  19. CSS3 animation动画 - 转风车、loding加载、人物走路等示例
  20. windows桌面待办事项_提醒待办事项app哪个好用?苹果手机上有什么好用的提醒便签软件吗...

热门文章

  1. [数据结构-严蔚敏版]P42多项式Polynomial的实现
  2. C++ 实现二叉树的非递归层次遍历(队列实现)
  3. 2019-03-06-算法-进化(三数之和)
  4. 1285. 单词 ac自动机 + fail树
  5. Codeforces Round #590 (Div. 3) F. Yet Another Substring Reverse 子集dp
  6. 【NOI2016】循环之美【莫比乌斯反演】【整除分块】【杜教筛】【类杜教筛】
  7. 数据结构(终极线段树篇)
  8. F. It‘s a bird! No, it‘s a plane! No, it‘s AaParsa!
  9. P5303 [GXOI/GZOI2019]逼死强迫症(斐波拉契、矩阵乘法)
  10. CF1415E New Game Plus(贪心)