springboot重定向致使的內存泄漏分析

以前在csdn寫過一次,今天晚上又遇到這個問題,在spring boot中還依舊存在。可是最近在找工做,因此copy到os中國,看看有沒有面試機會,若是您有坑位,麻煩聯繫我哈!base 廣深web

========================================================================================面試

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

========================================================================================緩存

項目內一個熱點接口,使用瞭如上代碼作代碼的重定向操做,而讓人沒想到的是:這樣子的寫法會有內存泄漏的風險mvc

若是不處理,那麼隨着內存會耗盡。最終會致使頻繁的fullgc,而OOM:gc overhead limit exceeded app

先說緣由:由於redirectUrl是帶參數,動態的連接,redirect會構建ViewResolver#create一個RedirectView,執行applyLifecycleMethods去initBean的時候,會使用了一個名爲AdvisedBeans的ConcurrentHashMap去保存,這個map以redirect:redirectUrl爲key,又沒有作limit容量限制,而redirectUrl是一個動態的連接,因此每次redirect都會生成put一條數據到map中jvm

圖爲pinpoint監控下,運行多天後,最近一天的內存變化圖,最後內存降下來是由於修改後重啓了。工具

舒適提示:若是您對分析過程沒興趣,那麼看到這裏就能夠中止了,解決方案之一是採用HttpServletResponse的sendRedirect就能夠解決。
如下爲我今天的處理、分析過程post

發現fullgc如此頻繁以後,首先是使用jmap dump出對應的堆hprof文件, jmap -dump:format=b,file=map.hprof pid
使用jstack導出線程信息, jstack pid >> stack.log
保存好gc的日誌,gc.log,由於咱們在一開始的jvm啓動參數上就會對gc日誌作保存
重啓項目,防止oom
下載mat工具對map.hprof文件進行分析
分析圖以下this

 

 能夠發現存在內存泄漏的狀況,罪魁禍首是被ConcurrentHashMap(advisedBeans)引用的字符串,查看map的內容,發現以下,ref的key所有都是redirect開頭的字符串,因此判斷問題出在springmvc的redirect上

可是僅僅知道問題出在這裏,仍是不知道怎麼解決,因此下面是簡單的源碼的一個分析過程,只寫關鍵部分,能夠按步驟本身對着源碼看

  首先將斷點定位到org.springframework.web.servlet.DispatcherServlet#doService()=>doDispatch()下,至於爲何?可能您須要瞭解一下springmvc的請求處理流程
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    //....
     this.doDispatch(request, response);
}
 

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

  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);
            }
        } 
   //....
    
}
 

  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<String, Object> model, Locale locale, HttpServletRequest request) throws Exception {
      //....
       view = viewResolver.resolveViewName(viewName, locale);
      //....
    }
private final Map<Object, View> viewAccessCache = new ConcurrentHashMap(1024);
    private final Map<Object, View> viewCreationCache = new LinkedHashMap<Object, View>(1024, 0.75F, true) {
        protected boolean removeEldestEntry(Entry<Object, View> 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;
        }
    }
 

調用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);
    }
 

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

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);
        }
      //....
    }
 

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;
    }
 

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;
    }
 

在這裏會將viewName做爲key,value=Boolean.False存入名advisedBeans的concurrentHashMap中,須要注意的是,這個map的無限的。 private final Map<Object, Boolean> 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;         }     } 至此,分析結束,也驗證了咱們的猜測,謝謝您看到這裏

相關文章
相關標籤/搜索