`

ClassLoader类加载内存泄漏问题

    博客分类:
  • java
阅读更多

原文地址: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。

分享到:
评论

相关推荐

    lassLoader的关系以及如何防止ClassLoader内存泄漏

    在Java世界中,类加载器...在编写自定义类加载器或者使用第三方库时,需谨慎处理类加载和资源释放,以防止潜在的内存泄漏问题。通过遵循最佳实践和适当的设计模式,我们可以确保Java应用程序的高效和稳定运行。

    Android中内存加载dex

    使用如`LeakCanary`等工具可以帮助检测和修复内存泄漏问题。 10. ** ART与Dalvik的差异**:ART运行时与Dalvik相比,提供了预编译的优势,使得应用启动更快,运行更高效。但两者在内存管理和类加载方面有细微差别,...

    Java的类加载器

    这导致了类加载器的泄漏问题,需要注意对类加载器的生命周期管理。 7. **类加载器的源码分析** 对于深入理解类加载器的工作原理,阅读和分析JDK源码是非常有帮助的。例如,`java.lang.ClassLoader`的`loadClass()`...

    Android 内存泄露 Mat工具分析

    Android 内存泄露是一个常见的问题,它会导致应用程序的性能下降,甚至崩溃。Memory Analyzer Tool(MAT)是一个功能强大且广泛使用的内存泄露分析工具。本文将详细介绍如何使用 MAT 分析 Android 应用程序中的内存...

    ClassLoader总结

    由于每个类都有唯一的ClassLoader实例加载,所以不同ClassLoader加载的相同类实际上是不同的类,它们之间无法相互转换。 6. 类的卸载: Java虚拟机默认不会主动卸载类,除非JVM退出或者加载该类的ClassLoader被...

    黑马程序员------类加载器学习注意点

    通过阅读《黑马程序员------类加载器学习注意点》这篇博文,以及对`classLoader.txt`文件的分析,我们可以更深入地掌握类加载器的相关知识,这对于解决实际问题和提升系统性能具有极大的帮助。在实践中不断探索和...

    类加载机制PPT+代码

    类加载机制是Java虚拟机(JVM)运行时的核心机制之一,它负责将类的.class文件从磁盘或网络中加载到内存,...通过深入学习和实践,我们可以更好地优化程序性能,避免内存泄漏,甚至实现自定义类加载器以满足特定需求。

    Java类重新加载101对象类和类加载器Java开发Jav

    因此,合理地管理和设计自定义类加载器是防止内存泄漏的关键。 在“Java类重新加载101对象类和类加载器”的主题中,开发者还需要理解类加载的生命周期、类的可见性以及类加载器的层次结构。这些都是深入学习Java...

    JVM模拟内存泄漏代码

    本篇文章将通过一个简单的"JVM模拟内存泄漏代码"来深入探讨堆内存泄漏和元空间泄漏,帮助开发者理解和预防这类问题。 堆内存泄漏是指程序中的对象在不再被使用后,由于某些原因没有被垃圾收集器回收,导致堆内存...

    从JDK源码级别剖析JVM类加载机制

    然而,由于类加载器与系统类(如java.lang.Object)的关系,完全卸载一个类加载器很困难,这也是Java内存泄露的一个潜在原因。 6. **类加载器与热部署**: 在JDK源码中,类加载机制也为动态替换和更新类提供了可能...

    java中关于类加载的资料

    - 适当使用类加载器,避免类加载器泄露导致内存泄漏。 - 理解并利用双亲委派模型,确保类的唯一性。 - 在设计复杂的系统时,考虑使用自定义类加载器以实现特定的加载逻辑。 总之,理解Java中的类加载机制有助于...

    详解Java内存泄露的示例代码

    Java内存泄露的示例代码的知识点总结 Java内存泄露是指Java应用程序中由于某些原因导致的内存不能被正确释放,导致JVM ...通过正确释放资源,避免使用ThreadLocal,正确使用ClassLoader,可以避免Java内存泄露问题。

    Tomcat 5.0.18 ClassLoader source code insight

    例如,当Web应用被停止时,WebappClassLoader需要正确地释放所加载的类,以防止内存泄漏。 总结起来,通过对Tomcat 5.0.18 ClassLoader源码的分析,我们可以更深刻地理解Tomcat如何管理Web应用的类加载,以及如何在...

    Java类加载机制 PDF 下载

    理解类加载机制对于优化Java应用性能、解决内存泄漏和理解类的生命周期至关重要。 1. **加载**:在加载阶段,类加载器寻找并读取指定的.class文件,将其转换为二进制数据流,然后构建一个Class对象。类加载器有三种...

    03Java虚拟机是如何加载Java类的1

    了解类加载机制对于理解和优化Java应用程序的性能至关重要,因为错误的类加载配置可能导致类冲突、内存泄漏或其他运行时问题。同时,通过自定义类加载器,开发者可以实现特定的加载逻辑,例如加载外部或加密的类文件...

    class文件热加载,上传class文件实现热加载

    - Bootstrap ClassLoader加载JDK核心库(rt.jar),Extension ClassLoader加载JRE扩展目录下的jar,AppClassLoader加载应用的类路径(classpath)中的类。 2. **自定义类加载器**: - 开发者可以创建自定义类加载...

    外部Jar包加载到可执行Jar或War包相关代码

    - 保持类加载器的生命周期与被加载类的生命周期一致,避免内存泄漏。 - 注意安全问题,防止恶意代码被加载。 - 当多个类加载器同时加载相同类时,需考虑类的可见性和版本冲突问题。 通过以上知识点,我们可以...

    热加载类

    这包括识别何时需要加载新版本的类,以及如何安全地替换旧类,避免数据不一致或内存泄漏的问题。 5. 使用示例:文章可能会给出具体的代码示例,演示如何在实际项目中使用热加载技术。这可能包括配置步骤、测试用例...

    tomcat内存溢出问题.doc

    如果问题仍然存在,可能需要进一步排查代码中是否存在内存泄露,或者是否加载了过多的类和库。 此外,优化Tomcat的配置还包括限制每个应用程序的类加载器(ClassLoader)的行为,避免类冲突和资源浪费。可以考虑...

    JVM 运行时数据区域,垃圾回收机制,类加载机制三大功能详解.docx

    通过深入理解JVM的运行时数据区域、垃圾回收机制和类加载机制,开发者能够更好地优化Java应用程序的性能,避免常见的内存泄漏问题,并充分利用JVM的强大功能。这些核心概念不仅对于日常开发至关重要,也是深入学习...

Global site tag (gtag.js) - Google Analytics