JVM菜鸟进阶高手之路

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

问题现象

代码如下,使用 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命令,查看结果如下:

640?wx_fmt=png&wxfrom=5&wx_lazy=1关于jstat命令详情可以参考:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jstat.html

jvm参数调整如下:


  1. -Xmx20m

  2. -Xms20m

  3. -Xmn10m

  4. -XX:+UseParNewGC

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

0?wx_fmt=png

说明

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

内存相关简单说明

640?wx_fmt=png

图中参数:-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菜鸟进阶高手之路八(一些细节),里面的,当时依稀记得这个图应该是飞哥发给我的。640?wx_fmt=png

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

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

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

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

  • 新生代,适合复制算法

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

复制算法

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

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

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

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

备注:java根对象:

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

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

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

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

  • 等等。

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

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

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

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


  1. -XX:+UseParNewGC

  2. -XX:+UseConcMarkSweepGC

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

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

640?wx_fmt=png所以看到这个图的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 这套参数,那么结果就是如图可以解释了,并且每个参数比例啥的都可以理解了。640?wx_fmt=png

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

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



—————END—————



本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/331316.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

事务的状态(状态模式)

【0】README0.1)本文部分文字描述转自 “head first设计模式”,旨在学习 事务的状态(状态模式) 的基础知识;【1】应用场景一1.1)还记得成都市各大高校内的米源自动售卖机吗?售卖机的主要制造商…

ogm neo4j_Neo4J OGM与Quarkus

ogm neo4j在下面的视频中,我演示了一个使用Neo4J数据库和Neo4J OGM的Quarkus应用程序示例。 看一下GitHub上的示例项目 。 我为咖啡豆创建了一个示例域,其中包含我们可以查询和匹配的某些风味配置文件。 红色节点是来自某个国家/地区的咖啡豆&#xff…

什么是ThreadLocal

本文转载自 占小狼的博客前言在面试环节中,考察"ThreadLocal"也是面试官的家常便饭,所以对它理解透彻,是非常有必要的. 有些面试官会开门见山的提问: “知道ThreadLocal吗?”“讲讲你对ThreadLocal的理解”…

Java bytecode instruction listings

【0】README 0.1)these contents are shiped from https://en.wikipedia.org/wiki/Java_bytecode_instruction_listings Mnemonic Opcode (in hexadecimal) Opcode (in binary) Other bytes Stack [before]→[after] Description(no name)cb-fd these values ar…

为wmi执行例外_称之为例外?

为wmi执行例外虽然这是一个有关测试和Wiremock的Java示例,但它涉及一个更普遍的问题。 我们正在尝试重试Wiremock的verify方法,该方法可能会在我们要检查的端点被命中之前由测试调用。 在这种情况下,我们希望在几秒钟后循环尝试一次&#xf…

漫画:什么是MapReduce

转载自 脑洞有点大的 程序员小灰什么是MapReduce?MapReduce是一种编程模型,其理论来自Google公司发表的三篇论文(MapReduce,BigTable,GFS)之一,主要应用于海量数据的并行计算。MapReduce可以分…

orelse_可选的orElse vs orElseGet

orelseJava 8引入了一个很棒的Optional概念,我们在博客Java 8 Optional中进行了研究 。 因此,为了简要说明一下,Optional用于表示Optional对象或空值,而不是null引用。 这将有助于避免在我们尝试对空引用对象执行某些操作时发生空…

jvm(6)-java类文件结构(字节码文件)

【0】README0.1)本文部分文字描述转自 “深入理解jvm”,旨在学习类文件结构 的基础知识;0.2)本文荔枝以及荔枝的分析均为原创;0.3)下面的截图中有附注t*编号,不关乎博文内容;0.4&am…

你可能不知道的 10 条 SQL 技巧

转载自 58沈剑 开源中国 一、一些常见的SQL实践 (1)负向条件查询不能使用索引 select * from order where status!0 and stauts!1 not in/not exists都不是好习惯 可以优化为in查询: select * from order where status in(2,3) &#x…

夜神模拟器模拟安卓测试_使用模拟进行测试

夜神模拟器模拟安卓测试如果使用正确的方法,模拟对象将非常有用。 我在需要驱动软件开发使用的帖子中分享了一些使用Mock Objects的经验。 在这篇文章中,我分享了两件事 –使用模拟进行基于合同的测试。 –用于组织模拟代码的模式。 基于合同的测试 …

java记录类型_Java中的记录类型

java记录类型于2020年3月发布的JDK 14引入了记录 (预览语言功能),该记录提供了一种紧凑的语法来声明主要用于保存数据的类。 在记录中 ,所有低级,重复且容易出错的代码都类似于构造函数,访问器和通用方法&a…

代理模式(多线程实现状态监控)

【-1】README-1.1)本文部分文字描述转自“head first 设计模式”,旨在学习 远程代理对象 的基础知识;-1.2)多线程实现糖果自动售卖机监控程序为原创;-1.3)博文最后,转载了代理模式的定义&#…

一篇文章搞定面试中的二叉树

转载自 IOExceptioner 算法与数据结构 在上一篇介绍二叉树( Android面试题算法之二叉树 、红黑树详细分析,看了都说好),没看的读者建议先去了解了解,接下来再给大家带来一篇关于二叉树的文章。 最近总结了一些数据结…

清洁代码_清洁单元测试

清洁代码编写使用JUnit和某些模拟库的“单元测试”测试很容易。 即使测试甚至不是单元测试并提供可疑的价值,它们也可能产生使某些涉众满意的代码覆盖范围。 编写单元测试(在理论上是单元测试,但是比基础代码更复杂)因此也很容易编…

jvm(6)-Class字节码文件结构总结

【0】README 0.1)本文总结于 Clas字节码文件,旨在理清 Class字节码文件的大体结构; 【1】干货开始 对上图的分析(Analysis):A1)offset0 A1.1)头四个字节为CAFEBABE:表示…

Android面试题算法之二叉树

转载自 qing的世界 程序员小乐文章目录 前言二叉树的递归(深度优先)处理二叉树的层序处理(广度优先)总结“一、前言今年可谓是跌宕起伏的一年,幸好结局还算是圆满。开年的时候由于和公司CTO有过节,被"打入冷宫"&#…

java 读取 文本块_Java文本块

java 读取 文本块文本块是JDK增强建议( JEP 355 ),可以在JDK 13和14中用作预览语言功能。它计划在JDK 15中成为永久性功能。文本块是跨越多行并且不需要的String文字。对于大多数转义序列。 动机 在标准Java字符串中嵌入XML,JSON…

代理模式之虚拟代理(仅了解)

【0】README0.1)本文全文转自 “head first 设计模式”,旨在了解 虚拟代理动态代理;0.2)晚辈我 java.swing 烂到渣,没有写出干货荔枝,抱歉;【1】虚拟代理简述1)远程代理:…

红黑树详细分析

转载自 coolblog 算法与数据结构“一、红黑树简介红黑树是一种自平衡的二叉查找树,是一种高效的查找树。它是由 Rudolf Bayer 于1972年发明,在当时被称为对称二叉 B 树(symmetric binary B-trees)。后来,在1978年被 Leo J. Guibas 和 Robert…

rest api如何创建_REST:创建资源

rest api如何创建资源创建是常见的REST API操作。 在这篇文章中,我们将看到如何创建单个资源。 客户要求 通常,通过将POST请求发送到父集合资源来创建资源。 这将使用新生成的ID创建一个新的下属资源。 例如,对/ projects的POST请求可用于在…