JSTL引发的内存泄露


声明:本文转载自https://my.oschina.net/u/731542/blog/1560967,转载目的在于传递更多信息,仅供学习交流之用。如有侵权行为,请联系我,我会及时删除。

硬件环境:

广州华为服务器 RH2288V3 

32G  内存,4核,1T硬盘

软件环境:

JDK1.7  weblogic12  Centos6.5

事情经过:

      2017年10月30号晚上有个同事突然打电话说新版本发布后系统经常运行几天就无缘无故崩溃,让我这边帮忙定位下是不是程序问题。一般这种程序崩溃基本上都是内存泄露,但检查最近更新的代码并没有发现有什么问题,保险起见赶紧上服务器down 一个dump文件下来观察一下。

      拿到最新的dump文件之后用MAT分析,一看果然发现内存居高不下(内存占用达8G)。仔细观察问题的根源竟然是jstl!因为公司的项目运行多年,用的技术五花八门其中有不少页面用到JSTL..以下就是Memory Analyzer的截图:

可以看到ELEvaluator这个类有一个Map对象,里面的对象占用内存达到67108880b(67M)该对象,其直接或间接引用的内存达7152269600b(7G)所以基本可以断定导致内存泄露的元凶就是它了。然后查看该对象的源码发现里面果然有2个静态Map对象

而且很神奇的是这2个map对象都只有put和get但是没有删除的方法,也就是说这2个对象只会递增!同时因为这2个对象非public也就是说其它类也无法引用...这只能说是JSTL的一个BUG。然后仔细观察这2个对象使用的地方:

发现只要获取不到就会创建一个新的对象,也就是说这2个对象均可以删除。于是解决的办法来了:直接把sCachedExpressionStrings和sCachedExpectedTypes删掉。然后重新生成class并替换对应jar包的class。更新上去运行1个星期之后发现内存基本稳定在3G左右(高峰6G低峰1G)。

 

后来网上查找一下发现老外早就发现这个问题:

http://www.archivum.info/issues@commons.apache.org/2007-09/00118/(jira)-Commented-(EL-1)-(el)-Memory-Leak-EL-Cache-entries-never-removed.html

只是一直没人给出解决的方法,网上也有人说这个BUG是JSTL1.2的问题,之后的版本已经解决。但是因为项目已经交付,如果临时替换JAR包难保不会引发其它问题所以就先这样解决,以后有时间把JSTL从项目中移除出去。

*因为公司用的是weblogic,weblogic自身有很多jar包其中weblogic.server.merged.jar 把JSTL1.2包含进来,如果是使用weblogic做为服务器则要小心需要修改weblogic.server.merged.jar这里面的JSTL,不然改了也不会生效。因为按java类加载顺序,服务器的jar包加载顺序优先于项目的jar包所以如果用的是weblogic的同学则要小心这个坑。

     最后附上ELEvaluator.java的源码,有兴趣的同学可以研究下有没有更好的解决方案,有问题可以email我。我的邮箱地址是:hurrican_ok@126.com。因为是用jdgui.exe反编译的大家将就看下

package org.apache.taglibs.standard.lang.jstl;  import java.io.Reader; import java.io.StringReader; import java.text.MessageFormat; import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.apache.taglibs.standard.lang.jstl.parser.ELParser; import org.apache.taglibs.standard.lang.jstl.parser.ParseException; import org.apache.taglibs.standard.lang.jstl.parser.Token; import org.apache.taglibs.standard.lang.jstl.parser.TokenMgrError;  public class ELEvaluator {   static Map sCachedExpressionStrings = Collections.synchronizedMap(new HashMap());   static Map sCachedExpectedTypes = new HashMap();   static Logger sLogger = new Logger(System.out);   VariableResolver mResolver;   boolean mBypassCache;      public ELEvaluator(VariableResolver pResolver)   {     this.mResolver = pResolver;   }      public ELEvaluator(VariableResolver pResolver, boolean pBypassCache)   {     this.mResolver = pResolver;     this.mBypassCache = pBypassCache;   }      public Object evaluate(String pExpressionString, Object pContext, Class pExpectedType, Map functions, String defaultPrefix)     throws ELException   {     return evaluate(pExpressionString, pContext, pExpectedType, functions, defaultPrefix, sLogger);   }      Object evaluate(String pExpressionString, Object pContext, Class pExpectedType, Map functions, String defaultPrefix, Logger pLogger)     throws ELException   {     if (pExpressionString == null) {       throw new ELException(Constants.NULL_EXPRESSION_STRING);     }     Object parsedValue = parseExpressionString(pExpressionString);     if ((parsedValue instanceof String))     {       String strValue = (String)parsedValue;       return convertStaticValueToExpectedType(strValue, pExpectedType, pLogger);     }     if ((parsedValue instanceof Expression))     {       Object value = ((Expression)parsedValue).evaluate(pContext, this.mResolver, functions, defaultPrefix, pLogger);                  return convertToExpectedType(value, pExpectedType, pLogger);     }     if ((parsedValue instanceof ExpressionString))     {       String strValue = ((ExpressionString)parsedValue).evaluate(pContext, this.mResolver, functions, defaultPrefix, pLogger);                  return convertToExpectedType(strValue, pExpectedType, pLogger);     }     return null;   }      public Object parseExpressionString(String pExpressionString)     throws ELException   {     if (pExpressionString.length() == 0) {       return "";     }     Object ret = this.mBypassCache ? null : sCachedExpressionStrings.get(pExpressionString);     if (ret == null)     {       Reader r = new StringReader(pExpressionString);       ELParser parser = new ELParser(r);       try       {         ret = parser.ExpressionString();         sCachedExpressionStrings.put(pExpressionString, ret);       }       catch (ParseException exc)       {         throw new ELException(formatParseException(pExpressionString, exc));       }       catch (TokenMgrError exc)       {         throw new ELException(exc.getMessage());       }     }     return ret;   }      Object convertToExpectedType(Object pValue, Class pExpectedType, Logger pLogger)     throws ELException   {     return Coercions.coerce(pValue, pExpectedType, pLogger);   }      Object convertStaticValueToExpectedType(String pValue, Class pExpectedType, Logger pLogger)     throws ELException   {     if ((pExpectedType == String.class) || (pExpectedType == Object.class)) {       return pValue;     }     Map valueByString = getOrCreateExpectedTypeMap(pExpectedType);     if ((!this.mBypassCache) && (valueByString.containsKey(pValue))) {       return valueByString.get(pValue);     }     Object ret = Coercions.coerce(pValue, pExpectedType, pLogger);     valueByString.put(pValue, ret);     return ret;   }      static Map getOrCreateExpectedTypeMap(Class pExpectedType)   {     synchronized (sCachedExpectedTypes)     {       Map ret = (Map)sCachedExpectedTypes.get(pExpectedType);       if (ret == null)       {         ret = Collections.synchronizedMap(new HashMap());         sCachedExpectedTypes.put(pExpectedType, ret);       }       return ret;     }   }      static String formatParseException(String pExpressionString, ParseException pExc)   {     StringBuffer expectedBuf = new StringBuffer();     int maxSize = 0;     boolean printedOne = false;     if (pExc.expectedTokenSequences == null) {       return pExc.toString();     }     for (int i = 0; i < pExc.expectedTokenSequences.length; i++)     {       if (maxSize < pExc.expectedTokenSequences[i].length) {         maxSize = pExc.expectedTokenSequences[i].length;       }       for (int j = 0; j < pExc.expectedTokenSequences[i].length; j++)       {         if (printedOne) {           expectedBuf.append(", ");         }         expectedBuf.append(pExc.tokenImage[pExc.expectedTokenSequences[i][j]]);                  printedOne = true;       }     }     String expected = expectedBuf.toString();           StringBuffer encounteredBuf = new StringBuffer();     Token tok = pExc.currentToken.next;     for (int i = 0; i < maxSize; i++)     {       if (i != 0) {         encounteredBuf.append(" ");       }       if (tok.kind == 0)       {         encounteredBuf.append(pExc.tokenImage[0]);         break;       }       encounteredBuf.append(addEscapes(tok.image));       tok = tok.next;     }     String encountered = encounteredBuf.toString();           return MessageFormat.format(Constants.PARSE_EXCEPTION, new Object[] { expected, encountered });   }      static String addEscapes(String str)   {     StringBuffer retval = new StringBuffer();     for (int i = 0; i < str.length(); i++) {       switch (str.charAt(i))       {       case '\000':          break;       case '\b':          retval.append("\\b");         break;       case '\t':          retval.append("\\t");         break;       case '\n':          retval.append("\\n");         break;       case '\f':          retval.append("\\f");         break;       case '\r':          retval.append("\\r");         break;       case '\001':        case '\002':        case '\003':        case '\004':        case '\005':        case '\006':        case '\007':        case '\013':        default:          char ch;         if (((ch = str.charAt(i)) < ' ') || (ch > '~'))         {           String s = "0000" + Integer.toString(ch, 16);           retval.append("\\u" + s.substring(s.length() - 4, s.length()));         }         else         {           retval.append(ch);         }         break;       }     }     return retval.toString();   }      public String parseAndRender(String pExpressionString)     throws ELException   {     Object val = parseExpressionString(pExpressionString);     if ((val instanceof String)) {       return (String)val;     }     if ((val instanceof Expression)) {       return "${" + ((Expression)val).getExpressionString() + "}";     }     if ((val instanceof ExpressionString)) {       return ((ExpressionString)val).getExpressionString();     }     return "";   } } 

 

 

 

 

本文发表于2017年11月05日 10:34
(c)注:本文转载自https://my.oschina.net/u/731542/blog/1560967,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。如有侵权行为,请联系我们,我们会及时删除.

阅读 1662 讨论 0 喜欢 0

抢先体验

扫码体验
趣味小程序
文字表情生成器

闪念胶囊

你要过得好哇,这样我才能恨你啊,你要是过得不好,我都不知道该恨你还是拥抱你啊。

直抵黄龙府,与诸君痛饮尔。

那时陪伴我的人啊,你们如今在何方。

不出意外的话,我们再也不会见了,祝你前程似锦。

这世界真好,吃野东西也要留出这条命来看看

快捷链接
网站地图
提交友链
Copyright © 2016 - 2021 Cion.
All Rights Reserved.
京ICP备2021004668号-1