(三)碎碎念接口优化--GC异常

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/lovesummerforever/article/details/70243853

        促销系统在运行很长的一段时间里,突然报错了,影响了线上的一些功能。

上异常,报出的GC异常如下。

java.lang.RuntimeException: org.springframework.data.redis.serializer.SerializationException: Cannot deserialize; nested exception is org.springframework.core.serializer.support.SerializationFailedException: Failed to deserialize payload. Is the byte array a result of corresponding serialization for DefaultDeserializer?; nested exception is java.lang.OutOfMemoryError: GC overhead limit exceeded


        由于是线上问题,简单看表面是redis问题,根儿上是GC问题,于是不管三七二十一,先把系统重启了,重启后一段时间貌似是可以的,但是之后还是会报这个异常。

        尝试修改一

        而后仔细看了这个貌似是redis序列化问题,貌似是从redis中取出来的东西不能反序列化,导致内存中的数据越来越大,于是想到了之前促销系统的版本升级,可能促销系统的某个类添加了一个字段,然后升级了一个版本,但是对应的调用方没有升级版本,导致的序列化失败?所以把soa调用促销系统的jar,版本升级为最新的版本,但是重启后仍然还是报如上的那个错误,看样子问题不在于此。

        尝试修改二

        既然提示是序列化问题,难道是redis序列化方式有问题了?

        后来查看了spirng对redis序列化配置

<bean id="masterRedisTemplate" class="org.springframework.data.redis.core.RedisTemplate" p:connectionFactory-ref="masterJedisConnectionFactory">
		<property name="keySerializer" ref="stringRedisSerializer"/>
		<property name="hashKeySerializer" ref="stringRedisSerializer"/>
	</bean>

        难道value序列化有问题了?于是添加上了redis value的序列化

	<bean id="masterRedisTemplate" class="org.springframework.data.redis.core.RedisTemplate" p:connectionFactory-ref="masterJedisConnectionFactory">
		<property name="keySerializer" ref="stringRedisSerializer"/>
		<property name="hashKeySerializer" ref="stringRedisSerializer"/>
		<property name="valueSerializer" ref="stringRedisSerializer"/>
		<property name="hashValueSerializer" ref="stringRedisSerializer"/>
	</bean>


        结果在没有清除之前的缓存的时候,大面积报转换错误(object不能转string),因为之前的缓存没有清除,之前以默认jdk序列化方式存入到redis,当修改了redus value的序列化方式为stringRedisSerializer,所以从redis中取出数据是jdk流的方式序列化的,无法转化为string序列化方式,此时算是雪上加霜了。

        修改三,万能的重启

        于是又改了回去,由于该异常表象是redis异常,所以万能的招儿来了,重启redis会不会管事,于是在重启了A套和B套redis后,不再有异常了,诡异的是为啥重启redis不会报GC异常了?

        分析当时的“车祸现场”

        查看了当时GC相关情况,如下

        jvm设置参数如下

-Xms3g -Xmx3g -Xmn2g -XX:PermSize=256m -XX:MaxPermSize=512m -XX:SurvivorRatio=2 -XX:+DisableExplicitGC -XX:+UseParallelGC -XX:ParallelGCThreads=4 -XX:+UseParallelOldGC

        free查看系统内存

             total       used       free     shared    buffers     cached
Mem:      32827372   32013008     814364      13328      19352     468480
-/+ buffers/cache:   31525176    1302196
Swap:      8388604    1068836    7319768

        内存参数分析

        系统内存大概31g左右,其中该项目设置的JVM最大使用内存-Xmx3g -Xms最小使用内存3g,该内存不能扩展,以避免垃圾回收后jvm重新分配。

        -Xmn2g 设置年轻代大小为2g,所以老年代大小1g。

        持久代-XX:MaxPermSize大小设置为512m,-XX:SurvivorRatio=2 ,这是年轻代中eden与survivor大小比为2:1 

        -XX:+DisableExplicitGC禁止代码中显示调用GC,-XX:+UseParallelGC选择年轻代垃圾回收器为并行收集器,

        -XX:ParallelGCThreads=4当选择jvm parallel回收器时,设置垃圾回收器并行处理线程数为4,不设置默认为cpu核数(该机器中逻辑cpu核数8),

        -XX:+UseParallelOldGC配置年老代垃圾回收集方式为并行收集。


可以从下图中看出,当出现异常时各个jvm 内存区域变化。

        从上面参数来看,我们可以知道年轻代大小2g,年老代1g。

        堆内存使用情况


        永久代使用情况



        年轻代使用情况



        survivor区


        老年代



        younggc


        full gc




       google查询

        网上查询该异常:http://stackoverflow.com/questions/1393486/error-java-lang-outofmemoryerror-gc-overhead-limit-exceeded

        The GC throws this exception when too much time is spent in garbage collection for too little return, eg. 98% of CPU time is spent on GC and less than 2% of heap is recovered.


        This feature is designed to prevent applications from running for an extended period of time while making little or no progress because the heap is too small.


        You can turn this off with the command line option -XX:-UseGCOverheadLimit


        大概是GC花费时间天长了,占用了大部分cpu98%时间,cpu只有2%的时间在做业务逻辑,导致报错。

       百度查询

        从网上查询异常和当时jvm内存情况来看,上图中可以看到eden区内存空间大小以及youngGC是没有问题的,唯一的问题可能是分配给老年代1g内存不够,或者将近不够。这个异常是发生在GC占用大量时间,释放很少的空间的时候发生的,是第一种保护机制,而抛出的异常。

        解决办法,为了不抛出这个异常解决办法是添加参数-XX:-UseGCOverheadLimit,也就是不去限制,要是内存过小直接抛出OOM直接解决堆内存空间大小问题。

        老年代快满的时候,程序已经进入了full GC来试图回收空间状态,可以从图中看到一次full gc用了1110毫秒,按说垃圾回收没有那么频繁,也没有浪费大量的时间。

        线上是四台服务器,而这只是显示的一台服务器jvm情况。

        可能是1 分析数据的网站有误差 2 可能是四台中的其他一台或两台挂掉 ,由于没有“保留”车祸现场,也没有观察抛出异常的频率,只是通过第三方可视化jvm查看当时一台机器的情况,没有通过-verbose:gc -XX:+PrintGCDetails参数打印出当时内存使用情况,只是再重启完redis一切都恢复正常,不再报上面的错误,而且仅出现过一次的偶发异常,之前和之后再也没有发生过,不再叨叨了。


总结

        算是初步接触GC异常,最终也是猜测问题可能所在,也没有确定,要多尝试,多多test,加油



参考:http://www.cnblogs.com/hucn/p/3572384.html





展开阅读全文

没有更多推荐了,返回首页