2019独角兽企业重金招聘Python工程师标准>>>

硬件环境:

广州华为服务器 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 "";}
}

转载于:https://my.oschina.net/u/731542/blog/1560967

JSTL引发的内存泄露相关推荐

  1. java thread 内存泄露_记一次ThreadLocal引发的内存泄露

    概念 ​首先解释下内存溢出和内存泄露的概念.内存溢出一般指的是out of memory,也就是我们经常说的OOM,常发生在堆,方法区和方法栈.内存泄露指的是一段程序在申请内存空间后,无法释放已经申请 ...

  2. 用 Go STL 查询 DB 引发的内存泄露

    本文字数:5295 字 精读时间:10 分钟 也可在 5 分钟内完成速读 问题起因 这几天有一个 Go API service 经过定时监控发现占用的内存不断上涨,内存从初始的 70M 一直上升到超过 ...

  3. 实战Go内存泄露【转】

    最近解决了我们项目中的一个内存泄露问题,事实再次证明pprof是一个好工具,但掌握好工具的正确用法,才能发挥好工具的威力,不然就算你手里有屠龙刀,也成不了天下第一,本文就是带你用pprof定位内存泄露 ...

  4. 内存泄露解决记录——窗口资源释放

    前段时间在解决代码的内存泄露问题,解决了部分内存泄露问题. http://blog.csdn.net/enjolras/archive/2011/01/05/6117628.aspx 这篇文章起到了很 ...

  5. C++11 解决内存泄露问题的智能指针:shared_ptr、unique_ptr、weak_ptr

    我们经常听到内存泄漏,但是对这个抽象的概念一直没有什么理解,比如产生内存泄漏又将如何,我平时写程序从来不考虑这个等等.这篇的目的:第一,给大家实验实验内存泄露带来的问题,让大家直观感受内存泄露.第二, ...

  6. Android内存优化(一)之FinalizerDaemon和FinalizerWatchDog多线程内存泄露案例

    前期有一个内存泄露case跟多线程相关,简单记录如下: 问题描述 跑一晚上的内存测试后,会出现很多的内存泄露,泄露trace如下 In *********:2.0.0:2. * *********** ...

  7. ThreadLocal是否会引发内存泄露的分析 good

    这篇文章,主要解决一下疑惑: 1. ThreadLocal.ThreadLocalMap中提到的弱引用,弱引用究竟会不会被回收? 2. 弱引用什么情况下回收? 3. JAVA的ThreadLocal和 ...

  8. java 代码 内存泄露_如何用Java编写一段代码引发内存泄露

    Q:刚才我参加了面试,面试官问我如何写出会发生内存泄露的Java代码.这个问题我一点思路都没有,好囧. A1:通过以下步骤可以很容易产生内存泄露(程序代码不能访问到某些对象,但是它们仍然保存在内存中) ...

  9. 一次“内存泄露”引发的血案

    2017年末,手Q春节红包项目期间,为保障活动期间服务正常稳定,我对性能不佳的Ark Server进行了改造和重写.重编发布一段时间后,结果发现新发布的Svr的机器内存一直在上涨.如下图示: 观察后, ...

最新文章

  1. Filter 字符编码Filter 一
  2. pcb成型板aoi检测_基于AOI技术的PCB常见质量缺陷检测
  3. 2021年互联网租房行业洞察报告
  4. JUC系列四:任务的取消与关闭
  5. mysql close 出错_MySQL错误Forcing close of thread的两种解决方法
  6. China’s movie heroes 《红海行动》展现中国英雄本色
  7. mysql时间段查询语句_MySQL 如何查看慢查询语句
  8. 链表去重 保留第一个元素
  9. SharePoint 2010-在ribbon上添加表单,将默认control加到自定义group中
  10. 2018年值得推荐的20个Bootstrap网站模板
  11. 下载IDEA - 2020.1以及安装IntelliJ IDEA
  12. 啊哈算法—解救小哈(深度优先搜索)
  13. 密度聚类DBSCAN的matlab代码
  14. 从零双排java之打印流
  15. U盘图标不显示(转)
  16. iOS 应用架构 (二)
  17. IDA Crack so文件
  18. python高级编程
  19. preempt rt对pagefault的处理
  20. AcWing 1192. 奖金

热门文章

  1. Linux环境搭建 - update https://apt.repos.intel.com 报错
  2. 如何使用MySQL存储Emoji表情,UTF-8和UTF-8MB4字符编码有何区别?
  3. centos安装Nvidia显卡驱动(3090)
  4. 算法:最小公倍数的求解方法
  5. ROS机器人入门课程《ROS理论与实践》零基础教程(推荐课程)
  6. vue 使用vue-canvas-poster生成自定义海报
  7. 卡刷刷机包出现 Error 7
  8. 三极管工作原理分析,精辟、透彻,看后你就懂
  9. HTML5+获取设备信息
  10. 麦当劳可以免费添加可乐的!