`

探讨:spring mvc中redirect导致内存泄露?

 
阅读更多
在老外http://vard-lokkur.blogspot.com/2012/12/springs-web-mvc-redirect-to-memory-leak.html一文中,谈到了他发现的一个spring mvc 3以及之前版本可能存在的一个
redirect引起的内存泄露问题。例子为:

@RequestMapping(method = RequestMethod.POST)
public String onPost(...) {
    ...
    return "redirect:form.html?entityId=" + entityId;
}




  You may start from reading Resolving views in Spring Framework documentation, and then take a closer look at the source code of AbstractCachingViewResolver, which is base class for many different View Resolvers in Spring, including: JSP, FreeMarker, Velocity, Jasper Reports, Tiles and XSLT view resolvers.

When resolveViewName method is called on AbstractCachingViewResolver it uses HashMap based view cache to speed up view resolving in the future calls, and cache key is by default created using view name and current locale.

Now to the clue - when you use the above method of redirecting, Spring Framework uses the whole String returned from your controller's method as the view name, including all parameters included in the target URL. Each time you perform the redirect, the parameters may vary, thus such a redirect will leave one additional entry in view cache of  AbstractCachingViewResolver, causing memory leak.


    他认为,只要这个形式,多次的执行的话(他执行了7000多次),看JVM大小设置问题会导致内存泄露,原因他分析说,AbstractCachingViewResolver,这个代码是众多视图解析的父类了,
当 resolveViewName 方法调用AbstractCachingViewResolver时,使用的是hashmap
缓存去加速,而key是默认使用viewname和当前的locale.而spring当调用
redirect这个时候,使用的是整个从controller方法中返回的字符串,做为view name,包括你的目标url,所以导致了memory leak(但为什么呢?好像原文作者没具体分析出来)
      作者还在https://jira.springsource.org/browse/SPR-10065开了一个jira说明,
有兴趣的读者可以去看下。他测试的DEMO的例子在:
https://github.com/vardlokkur/webapp-02可以下载。
   不过可惜我在自己的机器上,chrome下执行7000多次,没发现有问题,但IE下执行到1040
次,没报错,但就停止了,很奇怪,大家可以探讨尝试下。
  
7
0
分享到:
评论
3 楼 tomgxf 2016-11-24  
感谢楼主提供的线索,旧版本的确有问题。
在框架升级的时候我们发现该问题在新版本的springmvc中已经得到解决。缓存的map添加了默认数量限制。
private final Map<Object, View> viewCreationCache =
			new LinkedHashMap<Object, View>(DEFAULT_CACHE_LIMIT, 0.75f, true) {
				@Override
				protected boolean removeEldestEntry(Map.Entry<Object, View> eldest) {
					if (size() > getCacheLimit()) {
						viewAccessCache.remove(eldest.getKey());
						return true;
					}
					else {
						return false;
					}
				}
			};
2 楼 jackyrong 2012-12-09  
是的,在老外的这个jira中,后来也说到了addAttribute的方法了,呵呵
1 楼 zfc827 2012-12-09  
这个问题确实存在,主要原因是因为 springmvc 会缓存 view 的key。关键代码如下:
public abstract class AbstractCachingViewResolver extends WebApplicationObjectSupport implements ViewResolver {
    /** Whether we should cache views, once resolved */
    private boolean cache = true;

    /** Map from view key to View instance */
    private final Map<Object, View> viewCache = new HashMap<Object, View>();

    //... set/get方法略

    public View resolveViewName(String viewName, Locale locale) throws Exception {
        if (!isCache()) {
            return createView(viewName, locale);
        } else { 
            Object cacheKey = getCacheKey(viewName, locale);
            synchronized (this.viewCache) {
                View view = this.viewCache.get(cacheKey);
                if (view == null && (!this.cacheUnresolved || !this.viewCache.containsKey(cacheKey))) {
                    // Ask the subclass to create the View object.
                    view = createView(viewName, locale);
                    if (view != null || this.cacheUnresolved) {
                        this.viewCache.put(cacheKey, view);
                        if (logger.isTraceEnabled()) {
                            logger.trace("Cached view [" + cacheKey + "]");
                        }
                    }
                }
                return view;
            }
        }
    }
}

以上代码定义了一个 cache的标识位,默认为 true,并定义了一个 Map 来缓存 View,
如果在 controller 返回的 view 是不固定的,如:"redirect:form.html?entityId=" + entityId,由于 entityId 的值会存在 N 个,那么会导致产生 N 个 ViewName 被缓存起来。
由于内存大小是有限的,所以当 N 大到一定数量时,会产生内存溢出。

个人认为,这并不能算是 spring 的bug,更多的应当是我们的使用方式导致。
为了解决这个问题,spring 提供了其他的方案:http://static.springsource.org/spring-framework/docs/3.2.0.BUILD-SNAPSHOT/reference/htmlsingle/#mvc-ann-redirect-attributes

方法修改如下:
@RequestMapping(method = RequestMethod.POST)  
public String onPost(RedirectAttributes redirectAttrs) {  
    // ...
    redirectAttrs.addAttribute("entityId", entityId)
    return "redirect:form.html?entityId={entityId}";  
} 

如上,使用占位符后, viewName 的数量被限制为了1个。

相关推荐

Global site tag (gtag.js) - Google Analytics