ViewResolver与Redirect引起的内存泄漏
最近在生产环境中发现一个使用spring mvc的应用由于内存泄漏挂掉了。与大家分享一下我们发现的问题与解决方式
生产监控的现象
- 半个月内,内存的总体用量在不断上升,并且full GC频繁。
- 通过ha分析dump文件发现一个大量占用内存的对象,
在InternalResourceViewResolver对象中有一个hashMap包含多达四十万条记录,占用了98%的总体内存。
出现问题的应用功能介绍
- 应用负责一个相对独立的业务逻辑,所以web页面与java共同打在一个包内部署。
- 可以展示一些H5页面。
- java服务会将一些加密后的信息拼在url中,并发送redirect请求。
问题出现的原因
spring mvc通过ViewResolver和View来处理视图,在没有指定特殊的ViewResolver时,
默认使用InternalResourceViewResolver作为缺省的Resolver
而InternalResourceViewResolver继承自AbstractCachingViewResolver。
AbstractCachingViewResolver会将它曾经解析过的视图保持起来,以view的名称作为KEY。
每次要解析视图时先检查缓存,如果没有命中缓存则创建一个新视图并加入缓存中。以提高解析视图的性能。
public View resolveViewName(String viewName, Locale locale) throws Exception {
if(!this.isCache()) {
return this.createView(viewName, locale);
} else {
Object cacheKey = this.getCacheKey(viewName, locale);
View view = (View)this.viewAccessCache.get(cacheKey);
if(view == null) {
Map var5 = this.viewCreationCache;
synchronized(this.viewCreationCache) {
view = (View)this.viewCreationCache.get(cacheKey);
if(view == null) {
view = this.createView(viewName, locale);
if(view == null && this.cacheUnresolved) {
view = UNRESOLVED_VIEW;
}
if(view != null) {
this.viewAccessCache.put(cacheKey, view);
this.viewCreationCache.put(cacheKey, view);
if(this.logger.isTraceEnabled()) {
this.logger.trace("Cached view [" + cacheKey + "]");
}
}
}
}
}
return view != UNRESOLVED_VIEW?view:null;
}
}
然而在我们的应用场景下,url中包含了部分加密信息,每个redirectUrl都是不同的,那么每一个view也都是不同的,viewResolver将在缓存中插入大量的view。最终
导致内存耗尽,应用挂掉。
解决方式
在这里为大家提供几种解决方式。
1. 简单粗暴:将AbstractCachingViewResolver的cacheLimit属性设为0,以后任何view都不会被缓存(性能下降)。
由于我们的应用还会提供一些H5页面服务,所以需要采用一些更优雅的方式解决这个问题
- 重写一个类myViewResolver继承默认ViewResolver(InternalResourceViewResolver)。
- 重写父类AbstractCachingViewResolver的resolveViewName方法.
将myViewResolver设为默认的ViewResolver。
public class myViewResolver extends InternalResourceViewResolver {
@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
if (viewName.startsWith(“redirect”)){
return createView(viewName, locale);
}
return super.resolveViewName(viewName, locale);
}
这样一来,当生成的vivew是redirect时,直接创建一个新的view并返回不放入缓存中。如果是其他的view则还是原来的处理方式,放入缓存中进行加速。