前言
看过很多技术文档,也浏览过很多种技术方案,也实际上简单了解上手和面临过类似这种线上问题,但是一直没有一个很完整的一个记录,JVM调优,一个看起来很高大上,但是一个基本上现在的Java工程师都需要掌握和面试必须要经历的一个技术,但实际上能在公司真正的线上能去上手调优的机会少之极少,很多人可能谈吐起JVM,哗啦哗啦,但是实际上一上手,就可能会小鹿乱撞,但是线上的系统,实际上试错率几乎为0,所以这样的机会,在大型互联网,或者是互联网公司,是压根给不了这个尝试性去调优jvm的机会的,这次分享一个我们线上的问题,和我自己处理线上JVM的一次算是半完整的机会,此片博客仅为分享和记录,本人并不是一个专业的运维人员,对于JVM掌握的也微不足道。
1. 线上环境突然慢了下来,但是重启系统就能解决这个慢的问题,为什么?【可能比较啰嗦,但是我希望记录一次完整的记录】
1.1:记得刚刚进入新公司的时候,老大就说过线上系统的jvm的问题,但是系统也不至于说到不能用的这个份上,由于我们系统是一个比较大型的ERP系统,而且每次需求都是十分急促的,导致代码可能就" 你懂的 ",很大一部分原因也是每个人都很忙,需求也是并行开发,而需求是又多又杂乱(有客服的需求,有线上的bug,有财务的需求,还有需要重构,组织架构的等等需求),每个人都是各自忙碌自己的工作,也少了一项< code review>这个重要的环境,大概就真的变成的网上传言的 “千万别动我代码 ”,所以实际上很大一部分原因也是这个代码的不规范和不合理的写码导致的,但是压根没有时间去分析gc和线上系统的jvm问题,一直使用的都是默认的jvm配置,实际上是完全不合理的,突然有一天,也就是向前推移大约一周左右,系统需要12个小时左右重启一次,或者是说,系统慢下来了,甚至一些功能会直接超时,期间之前还导致过线上系统的OOM的情况发生,我们总共有三台服务器,曾经出现过宕机的情况。大体情况就是这样的一个情况,再介绍就没有过多的意义了。
2.线上系统的JVM排查过程,这里大概贴个图,时间紧,任务急,2天如何拯救线上环境,简单:派一个人专门负责重启系统就完事了,只是频率问题啦!
所以在这个一步步的同时也在请教大佬,去多取取经,吸收一下大佬们的解决方案,少走弯路,但是就如文章前言所说,真正上手线上系统的技术人员很少,但是确实很多大佬也给了一些建议,所以,也算是突破了一个受限,至少大概能从什么地方着手,下手,屁话不多说,开战!
3. 首先了解下几个JVM常用的一些命令和观察线上GC的一些基础命令,和线上环境的服务器分配比例
1、整个过程用到的Linux的一些操作命令(仅gc):
- jstat -gc PID 【gc日志】
- jmap -heap PID 【内存分配比例和使用率,实际上上面的命令也能看到,但是可能没有jmap观看起来这么方便】
- java -XX:+PrintCommandLineFlags-version 【查看线上使用的垃圾回收器】
- free -m 【这里留个伏笔,这个命令是后添加上去的,但是至于究竟是如何观察的,我自己也还在研究,没有想透彻,仅此分享一个案例】
【整个调优的过程我也就只是用到了以上的三个命令】 2、当然,jstat还有很多命令,这里就直接贴出来了,但是真正的我也只是用了其中的几个而已,大概就是如下:(这些东西是百度上的,基本上百度一下就有了)
jstat -gccapacity PID:堆内存分析 jstat -gcnewcapacity PID:年轻代内存分析 jstat -gcold PID:老年代GC分析 jstat -gcoldcapacity PID:老年代内存分析 jstat -gcmetacapacity PID:元数据区内存分析 jstat -gcnew PID:年轻代GC分析,这里的TT和MTT,可以看到对象在年轻代存活的年龄和存活的最大年龄
3、前提条件得准备充分,虽然之前对垃圾回收器是有比较拿手的一些方案和了解的垃圾回收器,但是真正是否使用,也是根据线上环境来的,于是我也提前了解好了,放入到了自己的本地文本文档上面,方便调试的时候,或者选择的时候更加的有针对性,更方便自己观看,这算是一种个人习惯,在下面给各位大佬贴出来:
Serial:几十兆 PS:上百兆 ~ 4G CMS:4G ~ 10G G1:10G~上百G (G1) ZGC: 4T - 16T(JDK13)
4、我们线上环境服务器分配比例:
1、沙箱环境的服务器:4核 * 16G; 2、正式环境的服务器:三台,分别为:4核 * 16G 两台,8 * 32G 一台。
具体介绍大约也就是这里的,准备工作虽然不够充足,但是也基本上算是紧急调试够用了。
4. 逼也装完了,差不多就到这里了【仅调情】
1、java -XX:+PrintCommandLineFlags-version 【首先我就看了线上默认的垃圾回收器使用的是那个垃圾回收器,关于垃圾回收器,到时候有空专门整理一份,一直没有闲下来的时间去整理,实际上很早之前就已经深入的了解过了一些,但是可能没有这么完整,可以借鉴,等写完贴到此处】
命令日志如下:【以下就能看出使用的是那种此时采用的是哪种垃圾回收器】
-XX:InitialHeapSize=515083264 -XX:MaxHeapSize=8241332224 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC java version “1.8.0_261” Java? SE Runtime Environment (build 1.8.0_261-b12) Java HotSpot? 64-Bit Server VM (build 25.261-b12, mixed mode)
2、jstat -gc PID 【敲完这个命令,出现的就是一下密密麻麻的一些东西了】 前提:需要对jvm有个大概了解的,可能看这篇文章没有如此的费劲,那么简单的再次截个图,标记几个重要的东西和重点观察的位置。
基本上圈出来,简单标注了一下下,我们简单的分析一下,首先简单的算一下这些个大概的数值,我们就以MB为单位。
1.72704KB ≈ 71 MB S-From区 2.7680KB ≈ 7MB S-To 区 卧槽,这不一下就看出问题来了,简直就是一个惊喜呀,第一次打日志就出现了问题,迅速飞转一下,为什么一下就看出问题了,可能稍微对gc有过稍稍了解的,就知道,堆内存的年轻代分区大约是这样的,我简短画个图,具体怎么演进的,大家可以去我的JVM专栏去看一眼。
也不花里胡哨,简单的介绍一下,现目前基本采用的回收算法都是复制算法,但是这个复制算法会分为两种,这种是演进后的,也就是比例8:1:1,演进之前的是5:5的,为什么这样,这里也只是简单介绍一下,因为只是实战分享,而不是技术分享,所以不过多解释,为了就是让年轻代空间更多的被利用起来,那么为什么说第一眼就看出问题了,因为一次垃圾回收大约是这样的。
此时出发一次gc,从打印日志的gc情况来看,Eden区域为:1868288KB ≈ 1868MB 也就是当对象占用一定的内存,就会发生一次年轻代的GC,也就是YOUNG GC或者是说教MINOR GC,总体来说就是年轻代GC,那么此时会发生的事情就是,s0区会将自己本身还存活着的对象全部放入到s1区,还将本次gc存活的对象全部放入s1区,那么就会清空s0区,这样复制和清空的好处就是防止内存碎片化。但是问题来了,此时s1区的空间只有7MB,但是S0区的空间有71MB,如果移动,推测,s1区可能装不下,90%以上的几率会直接进入到老年代区域,那么我们顺藤摸瓜,直接将老年代的空间计算出来 773632 KB ≈ 773MB 这样一看老年代还是能装一些对象的,那么我们顺着思路继续…
哎,打字有点累了,推荐一首歌曲,老劲爆了 【三天三夜】每周5天,加班三天,三夜是睡不好的
由上分析,老年代有773MB,我们先暂停脚步,分析一下Eden区的一个增长速度,也就是new XXX()对象产生的新对象的速度,这里注释下:会有高峰期和低谷期,建议取区间最大的也就是高峰期去分析,然后做空间和gc频率调整 增长前:708125.5KB≈708MB 增长后:724158.0 KB ≈ 724MB,简单做个计算题(724MB - 708MB)= 16MB,那么我们再计算一下Eden区域的总大小为:1868288.0 KB ≈ 1868MB,再做一个计算1868MB / 16MB ≈ 116.75秒,然后再除以 60 ,那么大约也就是2分钟左右就是一次年轻代的gc。这样我们就推算出来了年轻代gc的一个频率和时间。
接下来,写了很久了,也要下班跑路了,祝你们圣诞节快乐,俺要去过圣诞节啦,先更新到这里。
|