JVM菜鸟进阶高手之路十四:分析篇
转载请注明原创出处,谢谢!
题目回顾
JVM菜鸟进阶高手之路十三,问题现象就是相同的代码,jvm参数不一样,表现的现象不一样。
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参数配置如下:
-Xmx20m
-Xms20m
-Xmn10m
-XX:+UseParNewGC
-XX:+UseConcMarkSweepGC
-XX:+UseCMSInitiatingOccupancyOnly
-XX:CMSInitiatingOccupancyFraction=75
通过jstat命令,查看结果如下:
关于jstat命令详情可以参考:
https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jstat.html
jvm参数调整如下:
-Xmx20m
-Xms20m
-Xmn10m
-XX:+UseParNewGC
通过jstat命令,查看结果如下:
说明
上面的题目仅仅是一个切入点而已,希望通过一个切入点把jvm的一些基础知识刚刚好说明下,顺便解答下上面的现象。
内存相关简单说明
图中参数:
-Xms设置最小堆空间大小(一般建议和-Xmx一样)。
-Xmx设置最大堆空间大小。
-Xmn设置新生代大小。
-XX:MetaspaceSize设置最小元数据空间大小。
-XX:MaxMetaspaceSize设置最大元数据空间大小。
-Xss设置每个线程的堆栈大小(这里有个故事,3年前用正则表达式,后续有空正则表达式再说)。
备注:tenured空间就用减法操作即可明白,堆空间大小减去年轻代大小就可以了。
说到这里,下面这个几个参数应该明白了。
-Xmx20m
-Xms20m
-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的其他参数了。
-XX:+UseParNewGC
-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应该没有什么问题了吧。
-XX:+UseCMSInitiatingOccupancyOnly
-XX:CMSInitiatingOccupancyFraction=75
还有这2个参数关于cms的,-XX:+UseCMSInitiatingOccupancyOnly表示JVM不基于运行时收集的数据来启动CMS垃圾收集周期通过CMSInitiatingOccupancyFraction的值进行每一次CMS收集,-XX:CMSInitiatingOccupancyFraction=75 表示当老年代的使用率达到阈值75%时会触发CMS GC。
备注:jstat -gcutil可以看出上图的老年代的使用率才60.02%
还有最后一个参数解释:
-XX:+UseParNewGC
-XX:+UseParNewGC 表示新生代使用ParNew并行收集器,那么老年代呢?
可以让同样参数修改代码执行一次old gc即可看日志有类似[Tenured:说明老年代使用的是Serial Old
备注:Serial Old使用的是标记压缩算法。
解题
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();
}
说明:最后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行为一探究竟 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。
具体可以看看我比较崇拜的狼哥的分析,一个比我牛逼并且比我努力的大牛。一个有意思的频繁CMS问题
本人其他JVM菜鸟进阶高手之路相关文章或者其他系列文章可以关注公众号【匠心零度】获取更多!!!
如果读完觉得有收获的话,欢迎点赞、关注、加公众号【匠心零度】。
个人公众号,欢迎关注,查阅更多精彩历史!!!
JVM菜鸟进阶高手之路十四:分析篇的更多相关文章
- JVM菜鸟进阶高手之路十(基础知识开场白)
转载请注明原创出处,谢谢! 最近没有什么实战,准备把JVM知识梳理一遍,先以开发人员的交流来谈谈jvm这块的知识以及重要性,依稀记得2.3年前用solr的时候老是经常oom,提到oom大家应该都不陌生 ...
- JVM菜鸟进阶高手之路十二(jdk9、JVM方面变化, 蹭热度)
转载请注明原创出处,谢谢! 经过 4 次跳票,历经曲折的 Java 9 正式版终于发布了!今天看着到处都是jdk9发布了,新特性说明,心想这么好的蹭热度计划能错过嘛,哈哈,所以就发了这篇文章. 目前j ...
- JVM菜鸟进阶高手之路十三(等你来战!!!)
转载请注明原创出处,谢谢! 前几天有个朋友问了我个问题,下面给大家分享下,希望大家积极在评论区进行评论留言,等你来战!!! 先来个趣味题,热身下,引出后面的jvm题目. 地上的影子是那个人的? 地上的 ...
- JVM菜鸟进阶高手之路一[z]
https://mp.weixin.qq.com/s/qD1LFmsOiqZHD8iZX97OfA? 问题现象 代码如下,使用 ParNew + Serial Old 回收器组合与使用 ParNew ...
- JVM菜鸟进阶高手之路四
转载请注明原创出处,谢谢! 由于很多的jvm分析最好是基于gc日志的,所以添加参数如下即可: -verbose:gc -XX:+HeapDumpOnOutOfMemoryError -XX:+Prin ...
- JVM菜鸟进阶高手之路二(JVM的重要性,Xmn是跟请求量有关。)
转载请注明原创出处,谢谢! 今天看群聊jvm,通常会问ygc合适吗? 阿飞总结,可能需要2个维度,1.单位时间执行次数,2.执行时间 ps -p pid -o etime 查看下进程的运行时间, 17 ...
- JVM菜鸟进阶高手之路一(一次与笨神,阿飞近距离接触修改JVM)
转载请注明原创出处,谢谢! 今天在JVMPocket群里面看见,阿牛发了一个gc截图,之后ak47截图了特别恐怖,我就觉得好奇,去看看服务情况,截图日志如下 关于jstat命令详情可以参考:https ...
- JVM菜鸟进阶高手之路九(解惑)
转载请注明原创出处,谢谢! 在第八系列最后有些疑惑的地方,后来还是在我坚持不懈不断打扰笨神,阿飞,ak大神等,终于解决了该问题.第八系列地址:http://www.jianshu.com/p/7f7c ...
- JVM菜鸟进阶高手之路八(一些细节)
转载请注明原创出处,谢谢! gc日志问题 查看docker环境的gc日志,发现是下面这种情况,很奇怪,一直怀疑是docker环境那里是否有点问题,并没有怀疑配置,之前物理机上面的gc日志都是正常那种. ...
随机推荐
- 【特效】hover效果之十字动画
效果预览:http://www.gbtags.com/gb/rtreplayerpreview-standalone/3101.htm html: <div class="wrap&q ...
- 使用phpExcel导出excel时,报500错
在自己本地导出excel没有问题,但是放到服务器出现500的错误! 解决方法:查看控制器引用的header文件,是否包含空格,如下: header('Pragma:public'); ...
- Caused by: java.lang.IllegalArgumentException: 'dataSource' or 'jdbcTemplate' is required
概述 看到异常 一般就知道 在使用JdbcTemplate 需要购入数据源, 购入数据源的方式有两种,一种是xml 配置 在DAO层注入数据源, 另一种是在xml 中 配置模版JdbcTemplate ...
- session文件无法并发操作
session_start():打开服务器上的session文件. session_commit():会把$_SESSION数组的内容写入到服务器上的session文件中,但不会清空$_SESSION ...
- 探究Angular依赖注入对象$injector
$injector其实是一个IOC容器,包含了很多我们通过.module()和$provide创建的模块和服务.$injector服务提供了对依赖注入器对象的访问,当然我们也可以调用angular.i ...
- R学习笔记 第五篇:数据变换和清理
在使用R的分组操作之前,首先要了解R语言包,包实质上是实现特定功能的,预先写好的代码库(library),R拥有大量的软件包,许多包都是由某一领域的专家编写的,但并不是所有的包都有很高的质量的,在使用 ...
- C# linq对分组操作执行子查询
本主题演示创建查询的两种不同方式,此查询将源数据排序成组,然后分别对每个组执行子查询. 每个示例中的基本方法是使用名为 newGroup 的"接续块"对源元素进行分组,然后针对 n ...
- Linq Take和Skip详解
Take()方法的作用就是:从查询结果中提取前n个结果. Skip()方法正好是Take()方法的反面,它可以跳过前n个结果,返回剩余的结果. 例如:查找年龄最大的3个人 表Student的数据是 N ...
- React + Node 单页应用「一」前端搭建
项目地址 预览地址 原文地址 记录最近做的一个 demo,前端使用 React,用 React Router 实现前端路由,Koa 2 搭建 API Server, 最后通过 Nginx 做请求转发. ...
- Java IO编程全解(六)——4种I/O的对比与选型
转载请注明出处:http://www.cnblogs.com/Joanna-Yan/p/7804185.html 前面讲到:Java IO编程全解(五)--AIO编程 为了防止由于对一些技术概念和术语 ...