文档章节

一次生产的JVM优化

rock-man
 rock-man
发布于 01/02 14:37
字数 1650
阅读 3684
收藏 168

背景

    生产环境有二台阿里云服务器,均为同一时期购买的,CPU、内存、硬盘等配置相同。具体配置如下:

   

        节点

CPU

内存

硬盘

其它

A

2CPU            

4G

普通云盘

Centos6.4 64位+JDK1.8.0_121

B

2CPU            

4G

普通云盘

Centos6.4 64位+JDK1.8.0_121

    由于这二服务器硬件和软件配置相同,并且运行相同的程序,所以在Nginx轮询策略均weight=1,即平台的某个流量由这二台机器平分。

    有一次对系统进行例行检查,使用PinPoint查看下服务器”Heap Usage”的使用情况时,发现,在有一个系统Full GC非常频繁,大约五分钟一次Full GC(如果不明白Full GC的什么意思的,请自行百度),吓我一跳。这么频繁的Full GC,导致系统暂停处理业务,对系统的实时可用性大打折扣。我检查了一下Tomcat(Tomcat8.5.28)配置,发现在tomcat没有作任何关于JVM内存的设置,全部使用默认模式。由于这二服务器硬件和软件配置相同,并且运行相同的程序,所以在Nginx轮询策略均weight=1,即平台的某个流量由这二台机器平分。

GC数据

    在业务峰期间,通过PinPoint观察的A、B节点的”Heap Usage”使用情况,分别进行以下几个时间段数据。

    3小时图:

    

    上图B系统在三个小时内,一共发生了22次Full GC,大约每8分钟进行一次Full GC。每次Full GC的时间大概有150ms左右,即B系统在三个小时内,大约有3300ms暂停系统运行。从上图来看,堆的空间最大值在890M左右,但在堆空间的大小大约200M就发生Full GC了,从系统资源的利用角度来考虑,这个使用率太低了。

     

    上图A系统在3个小时内,一共发生了0次Full GC,嗯,就是没有任何停顿。 在这3小时,系统一直在处理业务,没有停顿。堆的总空间大约1536m,目前堆的空间大于500M。

    6小时图:

    

    上图B系统在6个小时的数据统计和3个小时很像,6个小时内一共发生了N次Full GC,均是堆的空间小于200M就发生Full GC了。

    

    上图A系统在6个小时内,一共发生了0次Full GC,表现优秀。

 12小时

    

    上图B系统在12个小时内,一共发生了N次Full GC,左边Full GC比较少,是因为我们的业务主要集中白天,虽然晚上属于非业务高峰期间,还是有Full GC。

    

    上图A系统在12个小时内,一共发生了0次Full GC,表现优秀。

GC日志

    看下gc.log文件,因为我们两台服务器都输出了gc的详细日志,先看下B系统的Full GC日志。

    

    

    上图全部是” [Full GC (Ergonomics)”日志,是因为已经去掉” GC (Allocation Failure”日志,这样更方便观察和分析日志,选取GC日志文件最后一条Full GC日志。

    2018-12-24T15:52:11.402+0800: 447817.937: [Full GC (Ergonomics) [PSYoungGen: 480K->0K(20992K)] [ParOldGen: 89513K->69918K(89600K)] 89993K->69918K(110592K), [Metaspace: 50147K->50147K(1095680K)], 0.1519366 secs] [Times: user=0.21 sys=0.00, real=0.15 secs]

    可以计算得到以下信息:

    堆的大小:110592K=108M

    老生代大小:89600K=87.5M

    新生代大小:20992K=20.5M

    分析:这次Full GC是因为老年代对象占用的空间的大小已经超过老年代容量 ([ParOldGen: 89513K->69918K(89600K)])引发的Full GC。是因为分配给老年代的空间太小,远远不能满足系统对业务的需要,导致老年代的空间常常被占满,老年代的空间满了,导致的Full GC。由于老年代的空间比较小,所以每次Full GC的时间也比较短。

    A系统日志,只有2次Full GC,这2次GC均发生在系统启动时:

    

    7.765: [Full GC (Metadata GC Threshold) [PSYoungGen: 18010K->0K(458752K)] [ParOldGen: 15142K->25311K(1048576K)] 33153K->25311K(1507328K), [Metaspace: 34084K->34084K(1081344K)], 0.0843090 secs] [Times: user=0.14 sys=0.00, real=0.08 secs]

    可以得到以下信息:

    堆的大小:1507328K=1472M

    老生代大小:89600K=1024M

    新生代大小:20992K=448M

    分析:A系统只有系统启动才出现二次Full GC现象,而且是” Metadata GC Threshold”引起的,而不是堆空间引起的Full GC。虽然经过一个星期的观察,A系统没有Full GC,但一旦发生Full GC时间则会比较长。其它系统增加发现过,1024M的老年代,Full GC持续的时间大约是90ms秒。所以看得出来推也不是越大越好,或者说在UseParallelOldGC收集器中,堆的空间不是越大越好。

分析与优化

总体分析:

  1. B系统的Full GC过于频繁,是因为老生代只有约108M空间,根本无法满足系统在高峰时期的内存空间需求。由于ParOldGen(老年代)常常被耗尽,所以就发生Full GC事件了。
  2. A系统的堆初始空间(Xms)和堆的最大值(Xmx)均为1536m,完全可以满足业务高峰期的内存需求。

优化策略:

  1. B系统先增加堆空间大小,即通过设置Xms、 Xmx值增加堆空间。直接把Xms和Xmx均设置为1024M。直接堆的启动空间(Xms)直接设置为堆的最大值的原因是:因为直接把Xms设置为最大值(Xmx)可以避免JVM运行时不停的进行申请内存,而是直接在系统启动时就分配好了,从而提高系统的效率。把Xms(堆大小)设置为1024M,是因为采用JDK的建议,该建议通过命令得到” java -XX:+PrintCommandLineFlags -version” 。其中,“-XX:MaxHeapSize=1004719104”,即Xmx为1024M,其它建议暂时不采纳。所以综合下来的B系统的JVM参数设置如下:export JAVA_OPTS="-server –Xms1024m -Xmx1024m -XX:+UseParallelOldGC  -verbose:gc -Xloggc:../logs/gc.log  -XX:+PrintGCDetails -XX:+PrintGCTimeStamps"
  2. A系统JVM参数设置保持不变,以便观察系统运行情况,即:export JAVA_OPTS="-server -Xms1536m -Xmx1536m -XX:+UseParallelOldGC  -verbose:gc -Xloggc:../logs/gc.log  -XX:+PrintGCDetails -XX:+PrintGCTimeStamps"
  1. 将A、B节点系统的JVM参数采用2套参数,是为了验证A或B的参数更适合实际情况。

 

 

© 著作权归作者所有

共有 人打赏支持
上一篇: JVM基础
下一篇: Paxos的通俗解释
rock-man
粉丝 14
博文 5
码字总数 11055
作品 0
深圳
私信 提问
加载中

评论(24)

rock-man
rock-man

引用来自“我还在等你回家”的评论

作者我想转发一下你这篇文章,留你的原文地址,可以嘛
可以的
我还在等你回家
我还在等你回家
作者我想转发一下你这篇文章,留你的原文地址,可以嘛
rock-man
rock-man

引用来自“orpherus”的评论

引用来自“rock-man”的评论

引用来自“orpherus”的评论

“一共发生了0次Full GC,嗯,就是没有任何停顿”

这个结论是不对的,不止full gc会stw,minor gc一样会stw。
是的,谢谢指正。
严格意义来说,分以下二种情况:
1、发生minor gc时,如果大部分Eden的对象不需要进阶到Survivor或Old时,stw的时间开销非常小,可以忽略不计。
2、发生minor gc时,大部分Eden的对象不能马上回收,而是需要进阶到Survivor或Old时,stw的时间开销则比较大,会导致程序停顿。

从Latency角度看minor gc影响不大,从throughput角度看,minor gc的比重可能更高。full gc耗时可能是minor gc的几百倍,但是minor gc次数可能是full gc的几万倍。
是的,没错。
l
leeqicoing
这里有没有搞Java开发的,欢迎大家加入我的Java技术交流QQ群:950449287,一起交流探讨工作中遇到的一些问题。
orpherus
orpherus

引用来自“rock-man”的评论

引用来自“orpherus”的评论

“一共发生了0次Full GC,嗯,就是没有任何停顿”

这个结论是不对的,不止full gc会stw,minor gc一样会stw。
是的,谢谢指正。
严格意义来说,分以下二种情况:
1、发生minor gc时,如果大部分Eden的对象不需要进阶到Survivor或Old时,stw的时间开销非常小,可以忽略不计。
2、发生minor gc时,大部分Eden的对象不能马上回收,而是需要进阶到Survivor或Old时,stw的时间开销则比较大,会导致程序停顿。

从Latency角度看minor gc影响不大,从throughput角度看,minor gc的比重可能更高。full gc耗时可能是minor gc的几百倍,但是minor gc次数可能是full gc的几万倍。
rock-man
rock-man

引用来自“儿子_我是你爸爸”的评论

0.084 90ms吧 900?
是的,谢谢指正,已修改。
rock-man
rock-man

引用来自“orpherus”的评论

“一共发生了0次Full GC,嗯,就是没有任何停顿”

这个结论是不对的,不止full gc会stw,minor gc一样会stw。
是的,谢谢指正。
严格意义来说,分以下二种情况:
1、发生minor gc时,如果大部分Eden的对象不需要进阶到Survivor或Old时,stw的时间开销非常小,可以忽略不计。
2、发生minor gc时,大部分Eden的对象不能马上回收,而是需要进阶到Survivor或Old时,stw的时间开销则比较大,会导致程序停顿。
儿子_我是你爸爸
儿子_我是你爸爸
0.084 90ms吧 900?
魔力猫
魔力猫

引用来自“魔力猫”的评论

已经JDK8了,为什么不尝试新的G1收集器呢?对于老年代积累过大造成FullGC的情况,G1应该比老策略更好才是。

引用来自“rock-man”的评论

关于我们生产环境不使用G1的原因可以参考下面的链接(我们生产环境只有4G内存):
https://docs.oracle.com/javase/7/docs/technotes/guides/vm/G1.html
另外,我认为合适前技术环境最重要,不建议只片面追求新技术。

引用来自“魔力猫”的评论

但是从文章来看,你们连对比尝试都没有做吧。而且G1也不算新技术了。

引用来自“rock-man”的评论

我没有生产环境进行CMS、G1的尝试,因为我们的生产环境只有4G内存,所以没有使用CMS、G1,并且ParallelOld停顿时间是我们可以接受范围之内的。
恰恰相反,我曾经花了大量的时间在测试环境,使用CMS、G1(基于4G内存)效果并不比ParallelOld好。

引用来自“魔力猫”的评论

既然做了对比测试,干嘛不写呢?精华实际上就是对比好不好。你等于把最有营养的地方掐掉了,然后文章逼格一下子降低到默认参数上线这种可以说是低级失误的层次了。

引用来自“rock-man”的评论

做了测试内容不一定要写的,我关于JVM做的测试多了,全部写上,看下文章的题目?我们生产环境要的是解决问题,不是什么X格,另外那是你觉得有营养(我保留个人观点),还是那句话,适合环境是最重要的。
没人逼你写,反正也就是把营养丰富的文章变成一份检讨书而已。
rock-man
rock-man

引用来自“魔力猫”的评论

已经JDK8了,为什么不尝试新的G1收集器呢?对于老年代积累过大造成FullGC的情况,G1应该比老策略更好才是。

引用来自“rock-man”的评论

关于我们生产环境不使用G1的原因可以参考下面的链接(我们生产环境只有4G内存):
https://docs.oracle.com/javase/7/docs/technotes/guides/vm/G1.html
另外,我认为合适前技术环境最重要,不建议只片面追求新技术。

引用来自“魔力猫”的评论

但是从文章来看,你们连对比尝试都没有做吧。而且G1也不算新技术了。

引用来自“rock-man”的评论

我没有生产环境进行CMS、G1的尝试,因为我们的生产环境只有4G内存,所以没有使用CMS、G1,并且ParallelOld停顿时间是我们可以接受范围之内的。
恰恰相反,我曾经花了大量的时间在测试环境,使用CMS、G1(基于4G内存)效果并不比ParallelOld好。

引用来自“魔力猫”的评论

既然做了对比测试,干嘛不写呢?精华实际上就是对比好不好。你等于把最有营养的地方掐掉了,然后文章逼格一下子降低到默认参数上线这种可以说是低级失误的层次了。
做了测试内容不一定要写的,我关于JVM做的测试多了,全部写上,看下文章的题目?我们生产环境要的是解决问题,不是什么X格,另外那是你觉得有营养(我保留个人观点),还是那句话,适合环境是最重要的。
jdk的fastdebug版本是什么

看到java大牛们的文章,各种不明觉厉。他们用一种叫fastdebug的jdk,好奇gooole一下,找到一篇仍然不明觉厉的英文说明。话说冰冻三尺非一日之寒,慢慢积累吧,翻译一下。 Ok, what the heck ...

yingtju
2018/06/26
0
0
79.项目由高版本的jdk开发 如何降低为低版本

0.jdk(linux中安装) 0.1 上传jdk到Linux系统 0.2 解压 0.3 配置 jdk环境(核心) (1)进入到上一步解压的jdk位子 如下图 (2) 配置内容 JAVA_HOME=/usr/local/develop/jdk/jdk-9.0.1 CLA...

Lucky_Me
2018/01/03
0
0
JVM性能优化, Part 5:Java的伸缩性

ImportNew注: JVM性能优化系列文章前4篇由ImportNew翻译(第一篇,第二篇,第三篇, 第四篇)。本文由新浪微博:吴杰 (@WildJay) 投稿至ImportNew。感谢吴杰! 如果你希望分享好的原创文章或...

梁杰_Jack
2014/10/30
0
0
JVM参数OmitStackTraceInFastThrow导致的异常栈信息不见了

问题描述 某天收到生产环境error日志告警(对error.log监控,超过一定大小就会给开发人员发送告警短信)。但是tail查看最新的异常信息只有这些,好忧伤: 后来有个同事从error.log前面开始看...

哲别0
2018/06/14
0
0
Java中for(;;)和while(true)有啥区别?

群里面有小伙伴提了个问题,说在看并发源码的时候,很多时候源码中都使用for(;;)而不是使用while(true),这两个有什么区别吗? 这种问题,在java里面,最简单有效的办法就是打印字节码看一下,...

若鱼1919
2018/07/11
0
0

没有更多内容

加载失败,请刷新页面

加载更多

大数据教程(11.9)hive操作基础知识

上一篇博客分享了hive的简介和初体验,本节博主将继续分享一些hive的操作的基础知识。 DDL操作 (1)创建表 #建表语法CREATE [EXTERNAL] TABLE [IF NOT EXISTS] table_name [(col_name ...

em_aaron
今天
0
0
OSChina 周四乱弹 —— 我家猫真会后空翻

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @我没有抓狂 :#今天听这个# 我艇牛逼,百听不厌,太好听辣 分享 Led Zeppelin 的歌曲《Stairway To Heaven》 《Stairway To Heaven》- Led Z...

小小编辑
今天
1
0
node调用dll

先安装python2.7 安装node-gyp cnpm install node-gyp -g 新建一个Electron-vue项目(案例用Electron-vue) vue init simulatedgreg/electron-vue my-project 安装electron-rebuild cnpm ins......

Chason-洪
今天
3
0
scala学习(一)

学习Spark之前需要学习Scala。 参考学习的书籍:快学Scala

柠檬果过
今天
3
0
通俗易懂解释网络工程中的技术,如STP,HSRP等

导读 在面试时,比如被问到HSRP的主备切换时间时多久,STP几个状态的停留时间,自己知道有这些东西,但在工作中不会经常用到,就老是记不住,觉得可能还是自己基础不够牢固,知识掌握不够全面...

问题终结者
昨天
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部