JVM调优学习之旅-(8)P+CMS案例优化分析记录与优化总结
参数案例
案例1-线上频繁Metadata GC
错误参数 -XX:SoftRefLRUPolicyMSPerMB=0
|
|
怎么查看到底什么类加载进入
|
|
通过这两个参数看看加载和卸载类的情况
|
|
在JVM运行期间不断地加载这个类
|
|
错误原因
clock - timestamp <= freespace * SoftRefLRUPolicyMSPerMB。 这个公式的意思就是说,“clock - timestamp”代表了一个软引用对象他有多久没被访问过了freespace代表JVM中的空闲内存空间,SoftRefLRUPolicyMSPerMB代表每一MB空闲内存空间可以允许SoftReference对象存活多久。
举个例子,假如说现在JVM创建了一大堆的奇怪的类出来,这些类本身的Class对象都是被SoftReference软引用的。然后现在JVM里的空间内存空间有3000MB,SoftRefLRUPolicyMSPerMB的默认值是1000毫秒,那么就意味着,此时那些奇怪的
SoftReference软引用的Class对象,可以存活3000 * 1000 = 3000秒,就是50分钟左右。 当然上面都是举例而已,大家都知道,一般来说发生GC时,其实JVM内部或多或少总有一些空间内存的,所以基本上如果不是快要发生OOM内存溢出了,一般软引用也是等到内存实在放不下对象才开始回收。
所以大家就知道了,按理说JVM应该会随着反射代码的执行,动态的创建一些奇怪的类,他们的Class对象都是软引用的,正常情况下不会被回收,但是也不应该快速增长才对
但是如果 把这个参数改为0的话 直接导致clock - timestamp <= freespace * SoftRefLRUPolicyMSPerMB这个公式的右半边是 0,就导致所有的软引用对象,比如JVM生成的那些奇怪的Class对象,刚创建出来就可能被一次Young GC给带着立马回收掉一些。接着在反射调用时又不断的创建类信息到元空间(因为JDK源码里的实现有一些问题,所以导致并发环境下会重复创建一些Class)。元空间满了就FGC。
这个参数一般设置大一些就可以了,没必要频繁的去对软引用的对象做回收。
案例2-大对象分析
分析案例前先看运行参数
|
|
堆大小1536M 年轻代512M 线程的栈大小 256k 年轻代比例5:1:1 意思就是 365:70:70左右 老年代1000m左右
-XX:CMSInitiatingOccupancyFraction=68 -XX:+UseCMSInitiatingOccupancyOnly 老年代占用68% 是启用回收 在占用680M时
CMSParallelRemarkEnabled 这是老年代在初始标记时采用并发标记
当时这个系统运行差不多30分钟左右进行一次FGC 意思就是差不多30分钟有680m左右对象进入老年代。
我们通过jstat 在线上观察JVM运行数据
并不是每次Young GC后都有几十MB对象进入老年代的,而是偶尔一次Young GC才 会有几十MB对象进入老年代,记住,是偶尔一次!
这也就是说600多M 进入老年代是比较困难的。
继续观察发现系统运行着,会有段时间会有几百M的数据直接进入老年代。
大对象问题
定位大对象 我是通过jstat 观察系统,一旦有大对象进入老年代。我就用jmap打印当时的内存快照。通过可视化工具 查看具体信息。分析对象来源。
这次大对象的来源是一个sql语句没加条件而把大量的数据查询出来造成的。
针对本次优化
让开发同学解决代码中的bug,避免一些极端情况下SQL语句里不拼接where条件,务必要拼接上where条件,不允许查询表 中全部数据。彻底解决那个时不时有几百MB对象进入老年代的问题。
年轻代明显过小,Survivor区域空间不够,因为每次Young GC后存活对象在几十MB左右,如果Survivor就70MB很容易触发 动态年龄判定,让对象进入老年代中。所以直接调整JVM参数如下:
1 2 3 4 5
-Xms1536M -Xmx1536M -Xmn1024M -Xss256K -XX:SurvivorRatio=5 -XX:PermSize=256M -XX:MaxPermSize=256M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=92 -XX:+CMSParallelRemarkEnabled -XX:+UseCMSInitiatingOccupancyOnly -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC
年轻代设置
直接把年轻代空间调整为700MB左右,每个Surivor是150MB左右,此时YGC过后就几十M存活对象,一般不会进入老年代
老年代设置
反之老年代就留500MB左右就足够了,因为一般不会有对象进入老年代。
而且调整了参数“XX:CMSInitiatingOccupancyFraction=92” 避免老年代仅仅占用68%就触发GC,现在必须要占用到92%才会触 发GC。(1.6后这个值时默认值92)
案例3 不要在业务代码使用System.gc()
|
|
针对这个问题,一方面大家平时写代码的时候,不要自己使用“System.gc()”去随便触发GC,
一方面可以在JVM参数中加入这 个参数:-XX:+DisableExplicitGC。这个参数的意思就是禁止显式执行GC,不允许你来通过代码触发GC。
所以推荐大家将“-XX:+DisableExplicitGC”参数加入到自己的系统的JVM参数中,或者是加入到公司的JVM参数模板中去。避免有 的开发工程师好心办坏事,代码中频繁触发GC就不好了。
阶段性总结
系统承载高并发请求,或者处理数据量过大,导致Young GC很频繁,而且每次Young GC过后存活对象太多,内存分配不合理, Survivor区域过小,导致对象频繁进入老年代,频繁触发Full GC。
系统一次性加载过多数据进内存,搞出来很多大对象,导致频繁有大对象进入老年代,必然频繁触发Full GC 系统发生了内存泄漏,
莫名其妙创建大量的对象,始终无法回收,一直占用在老年代里,必然频繁触发Full GC
Metaspace(永久代)因为加载类过多触发Full GC
误调用System.gc()触发Full GC
|
|
公司最好所有jvm模板参数都加上
|
|
基本参数
|
|
参数一定要自己设置,因为默认的参数会给予永久代 新生代的内存大小比较少。
系统的排查问题体系
一种成熟的监控方案
|
|
机器(CPU、磁盘、内存、网络)方面
cpu 负载高的问题,是否是GC太频繁导致 通过top看各方面指标
磁盘io问题 ,磁盘空间要有好的管控 ,这一块主要是怕其他操作把磁盘写满了。
内存这块,关注JVM 内存情况 gc频率。
代码异常这些捕获到的,都得上报到专门的分析、提醒平台。
实在监控方案没得, 就只能通过oom日志分析。能有当时的dump 最好。