ClassLoader类加载内存泄漏问题

原文地址:http://blog.csdn.net/hdy007/archive/2007/04/12/1562040.aspx 

3.1 全局集合
    在大型应用程序中存在各种各样的全局数据仓库是很普遍的,比如一个JNDI-tree或者一个session table。在这些情况下,必须注意管理储存库的大小。必须有某种机制从储存库中移除不再需要的数据。

    通常有很多不同的解决形式,其中最常用的是一种周期运行的清除作业。这个作业会验证仓库中的数据然后清除一切不需要的数据。

    另一种管理储存库的方法是使用反向链接(referrer)计数。然后集合负责统计集合中每个入口的反向链接的数目。这要求反向链接告诉集合何时会退出入口。当反向链接数目为零时,该元素就可以从集合中移除了。
  
    3.2 缓存
    缓存一种用来快速查找已经执行过的操作结果的数据结构。因此,如果一个操作执行需要比较多的资源并会多次被使用,通常做法是把常用的输入数据的操作结果进行缓存,以便在下次调用该操作时使用缓存的数据。缓存通常都是以动态方式实现的,如果缓存设置不正确而大量使用缓存的话则会出现内存溢出的后果,因此需要将所使用的内存容量与检索数据的速度加以平衡。

    常用的解决途径是使用java.lang.ref.SoftReference类坚持将对象放入缓存。这个方法可以保证当虚拟机用完内存或者需要更多堆的时候,可以释放这些对象的引用。

    3.3 类装载器
    Java类装载器的使用为内存泄漏提供了许多可乘之机。一般来说类装载器都具有复杂结构,因为类装载器不仅仅是只与"常规"对象引用有关,同时也和对象内部的引用有关。比如数据变量,方法和各种类。这意味着只要存在对数据变量,方法,各种类和对象的类装载器,那么类装载器将驻留在JVM中。既然类装载器可以同很多的类关联,同时也可以和静态数据变量关联,那么相当多的内存就可能发生泄漏。

   四. 如何检测和处理内存泄漏
    如何查找引起内存泄漏的原因一般有两个步骤:第一是安排有经验的编程人员对代码进行走查和分析,找出内存泄漏发生的位置;第二是使用专门的内存泄漏测试工具进行测试。

    第一个步骤在代码走查的工作中,可以安排对系统业务和开发语言工具比较熟悉的开发人员对应用的代码进行了交叉走查,尽量找出代码中存在的数据库连接声明和结果集未关闭、代码冗余等故障代码。

    第二个步骤就是检测Java的内存泄漏。在这里我们通常使用一些工具来检查Java程序的内存泄漏问题。市场上已有几种专业检查Java内存泄漏的工具,它们的基本工作原理大同小异,都是通过监测Java程序运行时,所有对象的申请、释放等动作,将内存管理的所有信息进行统计、分析、可视化。开发人员将根据这些信息判断程序是否有内存泄漏问题。这些工具包括Optimizeit Profiler,JProbe Profiler,JinSight , Rational 公司的Purify等。

 

下面内容原文地址: http://henryhu.yo2.cn/archives/5965

 

正如上所述Classloader类加载内存泄漏是由于不断重复地启动、停止应用导致Class多次加载,并由于系统一级对象引用的关系,导致以前创建的类无法被回收,从而导致内存泄漏。

 

定位ClassLoader 类加载内存泄漏的根源还是非常困难的,因为任何现有的JVM Profiling工具都不能通过ClassLoader的视角来分析当前内存中所存在的Class类。 目前我们只能通过产生JVM Heapdump的方式来鉴定是否存在ClassLoader 类加载内存泄漏,再通过以上ClassLoader 类加载内存泄漏产生的机理来排查可能出现问题的地方,最终解决问题。

好像在JDK6工具集中提供了相应的工具来定位问题。请参考http://blogs.sun.com/fkieviet/entry/how_to_fix_the_dreaded

为了简化大家排查和定位应用中可能存在ClassLoader 类加载内存泄漏的过程,为此我们罗列了一些能导致Classloader类加载内存泄漏的代码和组件:(我们此处就不铺开篇幅阐述下面组件导致内存泄漏的根源,就当作大家的作业吧!哈哈)

a) 应用或使用的组件中使用了java.util.logging.Level那你得注意了。

 

b) 如果使用了诸如DBCP等基于DriverManager API基础上开发的数据库连接池组件,如果底层设计考虑不周,极易引发Classloader类加载内存泄漏。

 

c) 如果你使用到了commons-logging组件(其实很多OpenSource组件都依赖于commons-logging),那十有八九会出现Classloader类加载内存泄漏。因为在象WebSphere、Tomcat等服务器核心引擎中同样使用到了commons-logging组件,并在应用启动之前commons-logging的很多类已经被系统级ClassLoader所加载。缺省状态下,一个类的加载是从JVM类加载器开始的,这样系统commons-logging的优先级一般高于应用EAR中所包含的commons-logging,所以Classloader类加载内存泄漏就有可能出现了。问题简单分析如下:

 

1) 我们一般在应用中使用commons-logging的API来获得Log:protected final Log logger = LogFactory.getLog(getClass())。

 

2) 为此我们分析commons-logging类库中LogFactory类,请注意其中factories是类静态变量,而getFactory()方法是静态方法,都是属于类属性。
通过下面代码我们可以清晰的得知:如果LogFactory在应用EAR上一级的类加载路径中被加载,那么在应用类加载器加载、创建的LogFactory实例(不管org.apache.commons.logging.impl.LogFactoryImpl还是org.apache.commons.logging.impl.Log4jFactory),将会被上一级类加载器中的LogFactory类所强制性地引用并存储在静态变量factories的类属性中。
故而即使强行停止此EAR应用,但是由于系统类加载器加载的LogFactory中的factories强制引用了此应用创建的LogFactory实例对象不能被进行垃圾回收,从导致所有的Class无法被销毁,最终形成Classloader类加载内存泄漏。

 

d) 把log4j类库放置到系统类路径下(比如:JVM、WebSphere Extensions Class loader、WebSphere lib/app Class loader、WebSphere "server" Class loader类路径),并且使用log4j的“Context Repository Selector”模式来获得各个应用的logging配置。
如果此时应用EAR/WAR中包含log4j类库将会出现Class Cast Exceptions异常不能正常运行;如果应用EAR/WAR中不包含log4j类库,虽然应用能够正常运行但是会导致Classloader类加载内存泄漏。关于log4j的“Context Repository Selector”模式请参考http://www.qos.ch/logging/sc.jsp

 

e) 如果你开发的组件中使用了java.beans.Introspector来进行Class/Method MetaData的缓存,可能会引发Classloader类加载内存泄漏。
每次解析Java Bean 的属性、方法是比较耗CPU资源的,所以在很多的框架级组件如Spring中普遍使用java.beans.Introspector来Cache缓存JavaBean的定义,Introspector类中会使用private static Map beanInfoCache = Collections.synchronizedMap(new WeakHashMap())类静态变量的方式来进行保存JavaBean的定义。
而Introspector是由系统JVM ClassLoader进行加载的,所以应用中定义的JavaBean Class将会被系统类加载器加载的Introspector强制引用,从而导致在应用被停止的状态下,所有与此应用相关的类无法被回收。

 

我们同样可以在Spring org.springframework.beans.CachedIntrospectionResults类的注释中,清晰的得知Spring中可能会存在Introspection Classloader类加载内存泄漏:“Internal class that caches JavaBeans {@link java.beans.PropertyDescriptor} information for a Java class. Not intended for direct use by application code. Necessary for own caching of descriptors within the application's ClassLoader, rather than rely on the JDK's system-wide BeanInfo cache (in order to avoid leaks on ClassLoader shutdown).”
在CachedIntrospectionResults中同样使用了类静态变量classCache来缓存类的定义,如果Spring的类库存在于应用类加载器上一级的JVM系统或应用服务器类路径上,则有可能导致Classloader类加载内存泄漏。

 

f) 在commons-beanutils 1.7版本(包括1.7版本)的组件中存在Classloader类加载内存泄漏,只有最新的1.8.0Beta修正了此潜在的问题,
问题描述:
* [BEANUTILS-59] - Memory leak on webapp undeploy in WrapDynaClass
* [BEANUTILS-156] - Memory leak on webapp undeploy in MappedPropertyDescriptor
详细描述请参考:http://commons.apache.org/beanutils/v1.8.0-BETA/RELEASE-NOTES.txt

g) 在应用中使用了commons-beanutils 的MethodUtils来对类的方法Method进行操作,那同样存在Classloader类加载内存泄漏的可能。
如果commons-beanutils类库放置在应用上一级的类加载路径中,并且有其他应用(或系统代码)在此应用之前使用同样方式MethodUtils来对Class的Method进行操作(在其他类加载器上加载MethodUitls),那么Classloader类加载内存泄漏必然出现。我们可以参考MethodUtils对应代码,可以非常直观地定位问题:

 

h) 如果应用中使用到Java 1.5语法定义的 enum 类,而此定义的类放置在应用上一级的类加载路径中。首先在我们开发的应用类加载器中加载并初始化了应用中定义的enum类,随后其他应用EAR/WAR(或系统代码)也使用到此定义的enum类,在并把此类enum属性引用放置到(针对其他应用的)类静态变量或Servlet类变量,那么我们开发应用的Classloader类加载器将不会被回收,最终内存泄漏必然出现。

 

i) 导致Classloader类加载内存泄漏的另外一个重要因素就是:如果在框架中或应用使用ThreadLocal线程数据空间来存储实例对象,你必须知道在WAS等应用服务器中线程实例都是属于池态的,是由应用服务器WebContainer等容器来维护这些线程实例。
即使应用被停止了,这些池态的线程实例仍然属于存活运行状态,如果应用Web Servlet线程运行过程中在ThreadLocal上存储的实例对象没有被正确删除,可能导致线程类加载内存泄漏问题。
在老版本的DOM4J、Mozilla Rhino、CGLIB都存在这种类型的线程内存泄漏,请使用这些组件的最新版本来避免此类泄漏问题的发生。

1) Hibernate 3.2.2版本中存在ThreadLocal 线程变量内存泄漏问题,在3.2.3版本中得到修订。详细内容请参考“Big memory leak in the use of CGLIB” http://opensource.atlassian.com/projects/hibernate/browse/HHH-2481

2) CGLIB 2.1存在ThreadLocal 线程变量内存泄漏问题,在最新的版本2.1_3中问题应该得到修订。详细内容请参考
http://sourceforge.net/tracker/index.php?func=detail&aid=1257327&group_id=56933&atid=482368
http://sourceforge.net/tracker/index.php?func=detail&aid=1291183&group_id=56933&atid=482370
http://jira.jboss.com/jira/browse/JBAS-2256

3) dom4j 1.6之前的版本存在ThreadLocal 线程变量内存泄漏问题,在1.6以后的版本中此问题得到解决。
问题描述:https://sourceforge.net/tracker/index.php?func=detail&aid=1070309&group_id=16035&atid=116035
Bug修订描述:“Added a SingletonStrategyclass for managing singletons. This allows to use different strategies for singletons, like: one instance per VM, one instance per thread, ... This change removed the usage of ThreadLocals.”
http://www.dom4j.org/changes-report.html
ClassLoader类加载内存泄漏问题的解决方案

ClassLoader类加载内存泄漏问题解决的基本原则:

1、 不要把应用使用的类库放置到JRE或WebSphere服务器的类加载器路径中,尽量把使用的类库保持在EAR 或WAR/WEB-INF/Lib路径中。

2、 尽量在WebSphere服务器中设置类加载顺序为“Child-First ClassLoaders”/“Parent-Last ClassLoaders”。

3、 针对DOM4J、Mozilla Rhino、CGLIB请确认使用最新的版本,并确认类库保存在应用EAR级别之下。

4、 尽量避免使用Java 1.5语法定义的 enum 类,如果使用了enum类,必须确认开发的类库保持在应用EAR类加载器这一级别之下,而千万不能放置到WebSphere或JVM类库路径中。

5、 使用最新版本的commons-logging,并确认类库保存在应用EAR级别之下。

6、 使用最新版本的commons-beanutils,并确认类库保存在应用EAR级别之下,千万不能放置到WebSphere或JVM类库路径中。

7、 使用最新版本的log4j,并确认类库保存在应用EAR级别之下,千万不能放置到WebSphere或JVM类库路径中。

8、 不要在生产环境中使用DriverManager。

9、 不要在生产环境中使用commons-dbcp作为数据源实现,推荐使用应用服务器提供的数据源。

10、 不要在应用中使用java.util.logging.Level。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值