以前在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; } } 至此,分析結束,也驗證了咱們的猜測,謝謝您看到這裏