springmvc重定向导致的内存泄漏分析

@RequestMapping(method = RequestMethod.GET)
public String test(String redirectUrl){
    return "redirect:"+redirectUrl;
}

项目内一个热点接口,使用了如上代码做代码的重定向操作,而让人没想到的是:这样子的写法会有内存泄漏的风险

如果不处理,那么随着内存会耗尽。最终会导致频繁的fullgc,而OOM:gc overhead limit exceeded 

先说原因因为redirectUrl是带参数,动态的链接,redirect会构建ViewResolver#create一个RedirectView,执行applyLifecycleMethods去initBean的时候,会使用了一个名为AdvisedBeans的ConcurrentHashMap去保存,这个map以redirect:redirectUrl为key,又没有做limit容量限制,而edirectUrl是一个动态的链接,所以每次redirect都会生成put一条数据到map中


图为pinpoint监控下,运行多天后,最近一天的内存变化图,最后内存降下来是因为修改后重启了

springmvc重定向导致的内存泄漏分析_第1张图片


温馨提示:如果您对分析过程没兴趣,那么看到这里就可以停止了,解决方案之一是采用HttpServletResponse的sendRedirect就可以解决。


以下为我今天的处理、分析过程

  1. 发现fullgc如此频繁之后,首先是使用jmap dump出对应的堆hprof文件, jmap -dump:format=b,file=map.hprof pid
  2. 使用jstack导出线程信息, jstack pid >> stack.log
  3. 保存好gc的日志,gc.log,因为我们在一开始的jvm启动参数上就会对gc日志做保存
  4. 重启项目,防止oom
  5. 下载mat工具对map.hprof文件进行分析

分析图如下

springmvc重定向导致的内存泄漏分析_第2张图片

 可以发现存在内存泄漏的情况,罪魁祸首是被ConcurrentHashMap(advisedBeans)引用的字符串,查看map的内容,发现如下,ref的key全部都是redirect开头的字符串,所以判断问题出在springmvc的redirect上springmvc重定向导致的内存泄漏分析_第3张图片

但是仅仅知道问题出在这里,还是不知道怎么解决,所以下面是简单的源码的一个分析过程,只写关键部分,可以按步骤自己对着源码看

  1.   首先将断点定位到org.springframework.web.servlet.DispatcherServlet#doService()=>doDispatch()下,至于为什么?可能您需要了解一下springmvc的请求处理流程
    protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //....
         this.doDispatch(request, response);
    }

     

  2.   在doDispatch()的最后,会调用org.springframework.web.servlet.DispatcherServlet#processDispatchResult()
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //....
        this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
    }

     

  3.   org.springframework.web.servlet.DispatcherServlet#render(),从而调用#resolverViewName()进行viewName的解析
    private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {
        //....
         if (mv != null && !mv.wasCleared()) {
                this.render(mv, request, response);
                if (errorView) {
                    WebUtils.clearErrorRequestAttributes(request);
                }
            } 
       //....
        
    }

     

  4.   org.springframework.web.servlet.view.AbstractCachingViewResolver#resolverViewName(),在这里会进行一次缓存的判断,生成一个cacheKey,cacheKey是由viewName(redirect:redirectUrl)+"_"+locale组成,存放到一个名为viewAccessCache的ConcurrentHashMap中,需要注意的是,这个map有做容量的上限限制为1024,具体做法是在存放到map的同时,也会存放一份到LinkedHashMap中,通过#removeEldestEntry去实现,所以如果redirectUrl是固定的,那么在第二次访问的时候,会直接命中缓存,也不会有内存泄漏的问题
    protected View resolveViewName(String viewName, Map model, Locale locale, HttpServletRequest request) throws Exception {
          //....
           view = viewResolver.resolveViewName(viewName, locale);
          //....
        }
    private final Map viewAccessCache = new ConcurrentHashMap(1024);
        private final Map viewCreationCache = new LinkedHashMap(1024, 0.75F, true) {
            protected boolean removeEldestEntry(Entry eldest) {
                if (this.size() > AbstractCachingViewResolver.this.getCacheLimit()) {
                    AbstractCachingViewResolver.this.viewAccessCache.remove(eldest.getKey());
                    return true;
                } else {
                    return false;
                }
            }
        };
    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;
            }
        }

     

  5. 调用org.springframework.web.servlet.view.UrlBasedViewResolver#createView(),判断到viewName startWith redirect:的话,那么会构建一个RedirectView,并调用org.springframework.web.servlet.view.UrlBasedViewResolver的#applyLifecycleMethods()
    protected View createView(String viewName, Locale locale) throws Exception {
            //....
            if (viewName.startsWith("redirect:")) {
                    forwardUrl = viewName.substring("redirect:".length());
                    RedirectView view = new RedirectView(forwardUrl, this.isRedirectContextRelative(), this.isRedirectHttp10Compatible());
                    view.setHosts(this.getRedirectHosts());
                    return this.applyLifecycleMethods(viewName, view);
                } 
           //....
        }
    private View applyLifecycleMethods(String viewName, AbstractView view) {
            return (View)this.getApplicationContext().getAutowireCapableBeanFactory().initializeBean(view, viewName);
        }

     

  6. org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#initializeBean()
    public Object initializeBean(Object existingBean, String beanName) {
            return this.initializeBean(beanName, existingBean, (RootBeanDefinition)null);
        }

     

  7. org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsAfterInitialization()
    protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) {
           //....
            if (mbd == null || !mbd.isSynthetic()) {
                wrappedBean = this.applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
            }
          //....
        }

     

  8. org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessAfterInitialization()
    public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName) throws BeansException {
            Object result = existingBean;
            Iterator var4 = this.getBeanPostProcessors().iterator();
    
            do {
                if (!var4.hasNext()) {
                    return result;
                }
    
                BeanPostProcessor beanProcessor = (BeanPostProcessor)var4.next();
                result = beanProcessor.postProcessAfterInitialization(result, beanName);
            } while(result != null);
    
            return result;
        }

     

  9. org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#wrapIfNecessary()
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            if (bean != null) {
                Object cacheKey = this.getCacheKey(bean.getClass(), beanName);
                if (!this.earlyProxyReferences.contains(cacheKey)) {
                    return this.wrapIfNecessary(bean, beanName, cacheKey);
                }
            }
    
            return bean;
        }

     

  10. 在这里会将viewName作为key,value=Boolean.False存入名advisedBeans的concurrentHashMap中,需要注意的是,这个map的无限的。
    private final Map advisedBeans = new ConcurrentHashMap(256);
    protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
            if (beanName != null && this.targetSourcedBeans.contains(beanName)) {
                return bean;
            } else if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
                return bean;
            } else if (!this.isInfrastructureClass(bean.getClass()) && !this.shouldSkip(bean.getClass(), beanName)) {
                Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, (TargetSource)null);
                if (specificInterceptors != DO_NOT_PROXY) {
                    this.advisedBeans.put(cacheKey, Boolean.TRUE);
                    Object proxy = this.createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
                    this.proxyTypes.put(cacheKey, proxy.getClass());
                    return proxy;
                } else {
                    this.advisedBeans.put(cacheKey, Boolean.FALSE);
                    return bean;
                }
            } else {
                this.advisedBeans.put(cacheKey, Boolean.FALSE);
                return bean;
            }
        }

    至此,分析结束,也验证了我们的猜想,谢谢您看到这里


    转载请注明出处哈

你可能感兴趣的:(spring)