最常见的优化是用工具判断出来每次Young GC后存活对象有多少,Eden区域过小,自然会导致频繁的触发Young GC,Survivor区域过小,自然会导致经常在Young GC之后存活对象其实也没多少,但就是Survivor区域放不下,通过调整区域比例,避免对象快速进入老年代。这是优化的第一步,也是最容易想到的。
此外,比如老年代有2G的内存,其中1.5G是连续可用内存,0.5G是很多内存碎片。本来老年代如果都是连续空内存的话,那么可能可以对象占用到将近2G才会触发Full GC。结果现在就是对象占用到了1.5G就需要触发Full GC了,剩下0.5G是没法放任何对象的。所以,在第一步降低了Full GC频率之后,由于老年代使用CMS,它的默认碎片整理次数是每5次Full GC后整理一下内存碎片,务必设置如下参数-XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0”,每次Full GC后都整理一下内存碎片,否则如果每次Full GC过后,都造成老年代里很多内存碎片,那么必然导致下一次Full GC更快到来。
引入两外两个参数。
第一个参数是-XX:+CMSParallelInitialMarkEnabled
。在使用CMS时的初始标记阶段,是会进行Stop the World
的,会导致系统停顿,可以打开参数,这个参数会在CMS垃圾回收器的“初始标记”阶段开启多线程并发执行,可以尽可能优化这个阶段的性能,减少Stop the World
的时间,
另外一个参数是-XX:+CMSScavengeBeforeRemark
,这个参数会在CMS的重新标记阶段之前,先尽量执行一次Young GC。CMS的重新标记也是会Stop the World的,所以所以如果在重新标记之前,先执行一次Young GC,就会回收掉一些年轻代里没有人引用的对象。所以如果先提前回收掉一些对象,那么在CMS的重新标记阶段就可以少扫描一些对象,此时就可以提升CMS的重新标记阶段的性能,减少他的耗时。
小型业务JVM模版
-Xms4096M -Xmx4096M -Xmn3072M -Xss1M -XX:PermSize=256M -XX:MaxPermSize=256M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFaction=92 -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0 -XX:+CMSParallelInitialMarkEnabled -XX:+CMSScavengeBeforeRemark
频繁Full GC不光是老年代触发的,有时候也会因为Metaspace区域的类太多而触发,也是就是Metaspace区域被占满,Full GC 的时候回收其中部分类,我们想了解Metaspace为什么被频繁占满,是哪个类不停的被加载进入,就可以使用下面的两个参数,很有用!
第一个参数是-XX:TraceClassLoading
,第二个参数是-XX:TraceClassUnloading
,分别追踪类加载和类卸载的情况,他会通过日志打印出来JVM中加载了哪些类,卸载了哪些类。对于这些类,通过查资料就会明白了,有一个案例说是java中反射时加载的类,执行反射代码时,JVM会在你反射调用一定次数之后就动态生成一些类。只要记住一个结论:如果你在代码里大量用了类似上面的反射的东西,那么JVM就是会动态的去生成一些类放入Metaspace区域里的。
反射过程中生成的类的Class对象,都是软引用的,正常情况下不会被回收!(什么是Class对象?类自己本身就是一个对象,就是一个Class的对象,一个Class对象代表了一个类,这个Class对象能够派生出很多的实例)
通过这个公式来判断clock - timestamp <= freespace * SoftRefLRUPolicyMSPerMB
,“clock - timestamp”代表了一个软引用对象他有多久没被访问过了,freespace代表JVM中的空闲内存空间,SoftRefLRUPolicyMSPerMB代表每一MB空闲内存空间可以允许SoftReference对象存活多久,默认是1000毫秒。假如JVM空闲空间为3000MB,那么一个软引用的对象最多可以活50分钟(3000*1000ms)。
按理说JVM应该会随着反射代码的执行,动态的创建一些奇怪的类,他们的Class对象都是软引用的,正常情况下不会被回收,但是也不应该快速增长才对,问题出在SoftRefLRUPolicyMSPerMB
这个参数,别误把它设置为0,一旦这个参数设置为0,任何软引用对象就可以尽快释放掉,但这样并不会提高内存利用率,JVM好不容易给你弄出来100个奇怪的类,结果因为瞎设置软引用的参数,导致突然一次GC就给你回收掉几十个类,接着JVM在反射代码执行的过程中,就会继续创建这种奇怪的类,在JVM的机制之下,会导致这种奇怪类越来越多。
解决方案:在有大量反射代码的场景下,只要把-XX:SoftRefLRUPolicyMSPerMB=0
,这个参数设置大一些即可,千万别让一些新手同学设置为0,可以设置个1000,2000,3000,或者5000毫秒,都可以。提高这个数值,就是让反射过程中JVM自动创建的软引用的一些类的Class对象不要被随便回收,优化这个参数之后,就可以看到系统稳定运行了。
场景是这样的,有一个线上系统一天频繁Full GC数十次,开始以为是每次Young GC后的存活对象较多,Survivor区域太小,放不下了,但通过jstat工具观察,发现每次Young GC后的对象很少,每次存活也就几十MB,且通过jstat追踪观察,并不是每次Young GC后都有几十MB对象进入老年代的,而是偶尔一次Young GC才会有几十MB对象进入老年代,记住,是偶尔一次!但是不知道为什么突然有几百MB对象进入老年代中,所以才导致Young GC偶尔一次让几十MB对象升入老年代,平均30分钟左右就会触发一次Full GC!这个几百MB的对象就是大对象,我们想要知道大对象是谁,可以利用堆内存快照工具jmap,通过使用这个工具我们发现是几个Map之类的数据结构,是从数据库中出来的,通过排查SQL语句,发现有一句select * from tbl
,根本没使用where语句,导致它把表中几十万条数据查出来,搞出来一个大对象。
解决方案,一是对SQL语句的处理,不要一次性查表里全部数据,二是给新生代分配更多的空间,毕竟每次到老年代的对象较少而且频率很低,同时给老年代调整了参数“-XX:CMSInitiatingOccupancyFraction=92”,避免老年代仅仅占用68%就触发GC。
**System.gc()
**不能随便瞎写,它每次执行都会指挥JVM去尝试执行一次Full GC,连带年轻代、老年代、永久代都会去回收,一秒一次Full GC太可怕了!如果写了这个代码,平时系统运行时,访问量很低,基本还不会出大乱子!但是在大促活动的时候,访问量一高,立马由System.gc()
代码频繁触发了Full GC,导致了这个系统直接被卡死了!
解决方案:一是平时写代码不要用System.gc()
去随便触发GC,二是在JVM参数中加入-XX:+DisableExplicitGC
,这个参数的意思就是禁止显式执行GC,不允许你来通过代码触发GC。
线上系统的机器CPU负载过高的两个常见的场景。
第一个场景,是你自己在系统里创建了大量的线程,这些线程同时并发运行,而且工作负载都很重,过多的线程同时并发运行就会导致你的机器CPU负载过高。
第二个场景,就是你的机器上运行的JVM在执行频繁的Full GC,Full GC是非常耗费CPU资源的,他是一个非常重负载的过程。所以一旦你的JVM有频繁的Full GC,带来的一个明显的感觉,一个是系统可能时不时会卡死。这可能与内存泄漏有关。
内存泄漏问题,就是内存里驻留了大量的对象塞满了老年代,导致稍微有一些对象进入老年代就会引发Full GC,而且Full GC之后还不会回收掉老年代里大量的对象,只是回收一小部分而已!(可以使用mat分析内存泄漏)
这个内存泄漏问题可能是没有限制JVM本次缓存大小,没有使用LRU之类的算法定期淘汰一些缓存里的数据,导致缓存在内存里的对象越来越多,进而造成了内存泄漏。
解决问题很简单,只要使用类似EHCache之类的缓存框架就可以了,他会固定最多缓存多少个对象,定期淘汰删除掉一些不怎么访问的缓存,以便于新的数据可以进入缓存中。
首先根据拥有的设备、每秒请求量、每秒创建多少个对象以及对象的大小等来估算1秒钟会占用多少内存空间,估算多久发生一次Young GC、每次存活下来多少对象,需要多少Survivor区,接着要去看老年代中对象的增长速度,多久发生一次Full GC,这样初步估算后去分配堆内存中新生代和老年代的空间,以及新生代中区域的比例。进入压测阶段。
在这里要分析Eden区对象的增长速度有多快,Young GC后有多少对象存活,Young GC频率和耗时,老年代对象增长速率,Full GC后有多少对象存活,Full GC的频率和耗时。压测完全可以通过jstat来看,然后我们调整参数,找到一些问题,主要还是避免对象频繁进入老年代引起频繁的Full GC。
系统上线后要使用监控工具如Zabbix、Open-Falcon等工具来监控运行情况。如果出现了CPU负载过高、频繁Full GC或者系统无法处理请求这种现象,都要去考虑是不是Full GC太频繁了。
频繁Full GC 的原因就是上面四个大标题