事件描述:系统task任务隔断时间经常无缘无故停止执行。
事件分析:1.单线程task由于线程阻塞,导致无法执行下一个task调度任务。每次都是通过重启应用进行解决。
但是阻塞的话有一个特店,一般情况下只会导致某个任务停止,不可能所有的task都会不执行,因此此点排除。
2.那会是什么原因?还会有什么情况会导致这个问题?因此想起了还有一种情况会导致这样的问题发生。那就是java虚拟机在执行fullgc的时候。稍后会简单介绍下相关知识。经通过排查,确认是由于内存泄漏,导致系统进行频繁fullgc导致的,稍后会具体介绍排查步骤以及解决方式。
3.什么是内存泄漏以及fullgc?
java的对象主要保存在内存中,如果对象不停的创建,则会占据越来越多的内存空间,而内存空间是有限的,长此以往,肯定会造成内存不足。因此java虚拟机需要进行对象的垃圾回收。java虚拟机会将不在使用的对象进行回收以释放内存空间,如果某些对象一直不能被java虚拟机回收,则会将其放入old区,如果这样的对象越来越多,则old区则会越来越大,如果old区的大小超过一定的阈值则会触发fullgc,java虚拟机在进行fullgc的时候,会暂停系统中所有线程的执行,专心来做fullgc,如果fullgc一直回收不掉,则会一直进行下去,这样的话就会导致系统所有线程都执行不了。(因此fullgc的次数越少,系统越稳健)内存泄漏:因为一直有对象回收不掉,导致系统可用内存越来越小,顾名思义好像内存空间泄漏了一样。这就是问题导致的原因,关于java虚拟机以及内存管理这块,可以参考【深入理解java虚拟机】这本书,这里不再详细赘述。
4.排查步骤:通过jmap -heap+进程号每天监控java内存使用状况,发现old区每天都在换换增加,当增加到99%时,系统task任务停止的情况发生了。因此可以确定问题已经找到,通过jmap-dump命令生成一个dump文件,copy到本地用Eclipse的mat工具进行分析发现sessionFactory对象占据过大,因此可以确定罪魁祸首便是他,通过展开这个对象的树形结构发现这里面有一个queryplancache对象占据过大,这个对象里存入的都是sql语句,本项目由于比较老化,持久层框架用的是hibernate,通过分析得知hibernate为了效率会缓存产生的sql语句,如果不进行缓存那么每次都要将hql或者实体映射转换成sql语句去请求数据库,这样太耗费性能,因此需要将转换的sql缓存起来,如果下一次遇到一样的查询,直接用缓存的sql去请求数据库这样的话可以提高性能。而hibernate缓存sql以及映射sql语句的条件中有一项是根据sql语句的参数来进行判定,如果没有用到占位符进行参数映射的话,hibernate都会认为是一条全新的数据,会将其缓存,因此对象会越来越大。
5.解决方案:将所有的参数映射改为占位符方式,这样的话,相同的参数查询只会缓存一条,同时将queryplancache的大小设置为一个比较小的值,默认为2048,其实是一种空间浪费。改完后部署上线。
6.每天监控内存使用情况,发现内存状况一切正常,而该种也没有再重现过,至此问题解决。