转载:
找到问题以及分析请参数转载博主相关文章:https://blog.csdn.net/qq_26093341/article/details/85696884
同时有相关提及问题博客:
https://jackyrong.iteye.com/blog/1744342
先上修改前和修改后代码比较:
未重启前:最小2017MB
刚重启后:241MB
通过MAT内存溢出分析工具分析如下:
******************************************以下拷贝自第一篇博主文章,以做记录****************************
-
@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监控下,运行多天后,最近一天的内存变化图,最后内存降下来是因为修改后重启了。
温馨提示:如果您对分析过程没兴趣,那么看到这里就可以停止了,解决方案之一是采用HttpServletResponse的sendRedirect就可以解决。
以下为我今天的处理、分析过程
- 发现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文件进行分析
分析图如下
可以发现存在内存泄漏的情况,罪魁祸首是被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;
-
}
-
}
至此,分析结束,也验证了我们的猜想,谢谢您看到这里
-