JVM 优化问题整理

查看jvm配置的gc垃圾回收机制

java  -XX:+PrintCommandLineFlags  -version 

查看linux 内存大小

cat /proc/meminfo | grep MemTotal

一.默认jvm参数问题,导致内存过低

因默认的jvm 新生代和老年代的内存占比很小,所以相对于4核8G的服务器 可以先根据一下参数模板进行配置:

-Xms4096M -Xmx4096M -Xmn3072M -Xss1M -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -
XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFaction=92 -
XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0 -XX:+CMSParallelInitialMarkEnabled -
XX:+CMSScavengeBeforeRemark -XX:+DisableExplicitGC -XX:+PrintGCDetails -Xloggc:gc.log -
XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/app/oom

jdk1.8之前 -XX:PermSize=256M -XX:MaxPermSize=256M 之后改为-XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M

-XX:HandlePromotionFailure

jdk7还需要设置-XX:HandlePromotionFailure,老年代空间担保,开启后当老年代的连续空间小于新生代对象的总大小时会根据历次晋升到老年代的对象的平均大小进行判断,小于历次晋升的平均大小就进行MinorGC,大于则先进行older gc

jdk8之后不需要,只要老年代的连续空间大于新生代对象的总大小或者历次晋升到老年代的对象的平均大小就进行MinorGC

-XX:+CMSParallelInitialMarkEnabled 

会在CMS垃圾回收器的“初始标记”阶段开启多线程并发执行

-XX:+CMSScavengeBeforeRemark

这个参数会在CMS的重新标记阶段之前,先尽量执行一次Young GC

CMS的重新标记也是会Stop the World的,所以所以如果在重新标记之前,先执行一次Young GC,就会回收掉一些年轻代里没有人引用的对象。
所以如果先提前回收掉一些对象,那么在CMS的重新标记阶段就可以少扫描一些对象,此时就可以提升CMS的重新标记阶段的性能,减少他的耗时。

二.CMS老年代碎片清理压缩设置

cms old gc 回收机制使用的是标记清理法 

-XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=5

CMSFullGCsBeforeCompaction 设置为5则说明在5次old gc后才会进行内存碎片压缩,所以这样对导致在第一次或第二次之后产生大量的内存碎片,造成连续可使用空间过小,没办法存入新进入的完整对象,这时就容易造成频繁的进行old gc。

设置 XX:CMSFullGCsBeforeCompaction=0,每次发生old gc都进行内存碎片压缩

三.运用大量反射的情况

Method method = XXX.class.getDeclaredMethod(xx,xx);
method.invoke(target,params);

如果你在代码里大量用了类似上面的反射的东西,那么JVM就是会动态的去生成一些类放入Metaspace区域里的。

-XX:TraceClassLoading -XX:TraceClassUnloading

追踪类加载和类卸载的情况,他会通过日志打印出来JVM中加载了哪些类,卸载了哪些类

加入这两个参数之后,我们就可以看到在Tomcat的catalina.out日志文件中,输出了一堆日志,里面显示类似如下的内容:
【Loaded sun.reflect.GeneratedSerializationConstructorAccessor from __JVM_Defined_Class】

在有大量反射代码的场景下,大家只要把-XX:SoftRefLRUPolicyMSPerMB=0这个参数设置大一些即可,千万别让一些新手同学设置为0,可以设置个1000,2000,3000,或者5000毫秒,都可以。

四.频繁触发老年代的大体原因

1.大对象问题

有时可能因为sql未加查询条件造成查询出大量数据产生大对象,导致大对象进入老年代,从而造成Old gc频繁,可以通过jmap和jhat组合查询是否有大对象,有的话查看大对象如何产生的,是否可以避免,如果避免不了,则对jvm内存进行调优。

2.新生代 Survivor 区域过于小,存不下每次ygc存活的对象

可通过-XX:SurvivorRatio=5 设置 新生代 eden:s:s的内存占比  

3.动态年龄判断 

几次Young GC过后,Surviovr区域中的对象占用了超过50%的内存,此时会判断如果年龄1+年龄2+年龄N的对象总和超过了Survivor区域的50%,此时年龄N以及之上的对象都进入老年代,这是动态年龄判定规则

4.对象在Survivor区域存活15次(默认)会进入老年代

通过-XX:MaxTenuringThreshold=15 可修改设置

-XX:CMSInitiatingOccupancyFraction=92 老年代占用到92%会触发GC。

五.代码调用触发GC

通过System.gc() 触发Full GC 可能会造成频繁gc(在老年代还有大量内存空间的情况下),所以在代码中不要自己使用触发,可通过设置-XX:+DisableExplicitGC(禁止显式执行GC,不允许你来通过代码触发GC)避免。

六.CPU负载过高

CPU负载过高的两个常见的场景:

第一个场景,是你自己在系统里创建了大量的线程,这些线程同时并发运行,而且工作负载都很重,过多的线程同时并发运行就会导致你的机器CPU负载过高。
第二个场景,就是你的机器上运行的JVM在执行频繁的Full GC。

七.系统中在JVM中做缓存

系统里做了一个JVM本地的缓存,把很多数据都加载到内存里去缓存起来,然后提供查询服务的时候直接从本地内存走。但是因为没有限制本地缓存的大小,并且没有使用LRU之类的算法定期淘汰一些缓存里的数据,导致缓存在内存里的对象越来越多,进而造成了内存泄漏。解决问题很简单,只要使用类似EHCache之类的缓存框架就可以了,他会固定最多缓存多少个对象,定期淘汰删除掉一些不怎么访问的缓存,以便于新的数据可以进入缓存中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值