SpringMVC中modelAndView重定向导致内存泄露

转载:

找到问题以及分析请参数转载博主相关文章:https://blog.csdn.net/qq_26093341/article/details/85696884

同时有相关提及问题博客:

https://jackyrong.iteye.com/blog/1744342

http://www.tianxiaohui.com/index.php/default/Spring-AspectJ-AOP-%E5%92%8C-redirect-ModelAndView-%E5%AF%BC%E8%87%B4%E7%9A%84%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F%E9%97%AE%E9%A2%98.html

 

先上修改前和修改后代码比较:

未重启前:最小2017MB

刚重启后:241MB

 

通过MAT内存溢出分析工具分析如下:

 

 

 

******************************************以下拷贝自第一篇博主文章,以做记录****************************

 
  1. @RequestMapping(method = RequestMethod.GET)

  2. public String test(String redirectUrl){

  3. return "redirect:"+redirectUrl;

  4. }

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

如果不处理,那么随着内存会耗尽。最终会导致频繁的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就可以解决。


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

  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文件进行分析

分析图如下

 可以发现存在内存泄漏的情况,罪魁祸首是被ConcurrentHashMap(advisedBeans)引用的字符串,查看map的内容,发现如下,ref的key全部都是redirect开头的字符串,所以判断问题出在springmvc的redirect上

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

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

    2. //....

    3. this.doDispatch(request, response);

    4. }

     

  2.   在doDispatch()的最后,会调用org.springframework.web.servlet.DispatcherServlet#processDispatchResult()
     
    1. protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {

    2. //....

    3. this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);

    4. }

     

  3.   org.springframework.web.servlet.DispatcherServlet#render(),从而调用#resolverViewName()进行viewName的解析
     
    1. private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {

    2. //....

    3. if (mv != null && !mv.wasCleared()) {

    4. this.render(mv, request, response);

    5. if (errorView) {

    6. WebUtils.clearErrorRequestAttributes(request);

    7. }

    8. }

    9. //....

    10.  
    11. }

     

  4.   org.springframework.web.servlet.view.AbstractCachingViewResolver#resolverViewName(),在这里会进行一次缓存的判断,生成一个cacheKey,cacheKey是由viewName(redirect:redirectUrl)+"_"+locale组成,存放到一个名为viewAccessCache的ConcurrentHashMap中,需要注意的是,这个map有做容量的上限限制为1024,具体做法是在存放到map的同时,也会存放一份到LinkedHashMap中,通过#removeEldestEntry去实现,所以如果redirectUrl是固定的,那么在第二次访问的时候,会直接命中缓存,也不会有内存泄漏的问题
     
    1. protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale, HttpServletRequest request) throws Exception {

    2. //....

    3. view = viewResolver.resolveViewName(viewName, locale);

    4. //....

    5. }

    6. private final Map<Object, View> viewAccessCache = new ConcurrentHashMap(1024);

    7. private final Map<Object, View> viewCreationCache = new LinkedHashMap<Object, View>(1024, 0.75F, true) {

    8. protected boolean removeEldestEntry(Entry<Object, View> eldest) {

    9. if (this.size() > AbstractCachingViewResolver.this.getCacheLimit()) {

    10. AbstractCachingViewResolver.this.viewAccessCache.remove(eldest.getKey());

    11. return true;

    12. } else {

    13. return false;

    14. }

    15. }

    16. };

    17. public View resolveViewName(String viewName, Locale locale) throws Exception {

    18. if (!this.isCache()) {

    19. return this.createView(viewName, locale);

    20. } else {

    21. Object cacheKey = this.getCacheKey(viewName, locale);

    22. View view = (View)this.viewAccessCache.get(cacheKey);

    23. if (view == null) {

    24. Map var5 = this.viewCreationCache;

    25. synchronized(this.viewCreationCache) {

    26. view = (View)this.viewCreationCache.get(cacheKey);

    27. if (view == null) {

    28. view = this.createView(viewName, locale);

    29. if (view == null && this.cacheUnresolved) {

    30. view = UNRESOLVED_VIEW;

    31. }

    32.  
    33. if (view != null) {

    34. this.viewAccessCache.put(cacheKey, view);

    35. this.viewCreationCache.put(cacheKey, view);

    36. if (this.logger.isTraceEnabled()) {

    37. this.logger.trace("Cached view [" + cacheKey + "]");

    38. }

    39. }

    40. }

    41. }

    42. }

    43.  
    44. return view != UNRESOLVED_VIEW ? view : null;

    45. }

    46. }

     

  5. 调用org.springframework.web.servlet.view.UrlBasedViewResolver#createView(),判断到viewName startWith redirect:的话,那么会构建一个RedirectView,并调用org.springframework.web.servlet.view.UrlBasedViewResolver的#applyLifecycleMethods()
     
    1. protected View createView(String viewName, Locale locale) throws Exception {

    2. //....

    3. if (viewName.startsWith("redirect:")) {

    4. forwardUrl = viewName.substring("redirect:".length());

    5. RedirectView view = new RedirectView(forwardUrl, this.isRedirectContextRelative(), this.isRedirectHttp10Compatible());

    6. view.setHosts(this.getRedirectHosts());

    7. return this.applyLifecycleMethods(viewName, view);

    8. }

    9. //....

    10. }

    11. private View applyLifecycleMethods(String viewName, AbstractView view) {

    12. return (View)this.getApplicationContext().getAutowireCapableBeanFactory().initializeBean(view, viewName);

    13. }

     

  6. org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#initializeBean()
     
    1. public Object initializeBean(Object existingBean, String beanName) {

    2. return this.initializeBean(beanName, existingBean, (RootBeanDefinition)null);

    3. }

     

  7. org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsAfterInitialization()
     
    1. protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) {

    2. //....

    3. if (mbd == null || !mbd.isSynthetic()) {

    4. wrappedBean = this.applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);

    5. }

    6. //....

    7. }

     

  8. org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessAfterInitialization()
     
    1. public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName) throws BeansException {

    2. Object result = existingBean;

    3. Iterator var4 = this.getBeanPostProcessors().iterator();

    4.  
    5. do {

    6. if (!var4.hasNext()) {

    7. return result;

    8. }

    9.  
    10. BeanPostProcessor beanProcessor = (BeanPostProcessor)var4.next();

    11. result = beanProcessor.postProcessAfterInitialization(result, beanName);

    12. } while(result != null);

    13.  
    14. return result;

    15. }

     

  9. org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#wrapIfNecessary()
     
    1. public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {

    2. if (bean != null) {

    3. Object cacheKey = this.getCacheKey(bean.getClass(), beanName);

    4. if (!this.earlyProxyReferences.contains(cacheKey)) {

    5. return this.wrapIfNecessary(bean, beanName, cacheKey);

    6. }

    7. }

    8.  
    9. return bean;

    10. }

     

  10. 在这里会将viewName作为key,value=Boolean.False存入名advisedBeans的concurrentHashMap中,需要注意的是,这个map的无限的。
     
    1. private final Map<Object, Boolean> advisedBeans = new ConcurrentHashMap(256);

    2. protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {

    3. if (beanName != null && this.targetSourcedBeans.contains(beanName)) {

    4. return bean;

    5. } else if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {

    6. return bean;

    7. } else if (!this.isInfrastructureClass(bean.getClass()) && !this.shouldSkip(bean.getClass(), beanName)) {

    8. Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, (TargetSource)null);

    9. if (specificInterceptors != DO_NOT_PROXY) {

    10. this.advisedBeans.put(cacheKey, Boolean.TRUE);

    11. Object proxy = this.createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));

    12. this.proxyTypes.put(cacheKey, proxy.getClass());

    13. return proxy;

    14. } else {

    15. this.advisedBeans.put(cacheKey, Boolean.FALSE);

    16. return bean;

    17. }

    18. } else {

    19. this.advisedBeans.put(cacheKey, Boolean.FALSE);

    20. return bean;

    21. }

    22. }

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

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值