Java应用程序中的已加载类数中可能的内存泄漏
我最近开始分析我使用VisualVM编写的osgi Java应用程序。我注意到的一件事是,当应用程序开始(通过JMS)向客户端发送数据时,已加载类的数量开始以稳定的速度增加。但是,堆大小和PermGen大小保持不变。即使停止发送数据,类的数量也永远不会减少。这是内存泄漏吗?我认为是这样,因为已加载的类必须存储在某个位置,但是即使我将应用程序运行了几个小时,堆和permgen也不会增加。
要获取我的分析应用程序的屏幕截图,请点击此处
解决方案
是的,这通常是内存泄漏(由于我们实际上并不直接处理内存,因此更多的是类实例泄漏)。我之前已经完成了此过程,通常是将一个侦听器添加到了一个旧工具箱中,但并没有将其自身删除。
在较早的代码中,侦听器关系导致"侦听器"对象保持存在。我会看一下较旧的工具包,或者尚未经历过多次修订的工具包。在更高版本的JDK上运行的任何长期存在的库都将了解引用对象,从而消除了"删除侦听器"的要求。
另外,如果我们每次都重新创建窗口,请在Windows上调用dispose。我认为,如果我们不这样做,他们永远也不会消失(实际上,在封闭环境中也有处置)。
不必担心Swing或者JDK侦听器,它们都应该使用引用,所以我们应该没事。
除非我误会,否则我们在这里查看的是已加载的类,而不是实例。
当代码首次引用一个类时,JVM使ClassLoader退出并从.class文件等中获取有关该类的信息。
我不确定在什么条件下可以卸载类。当然,它绝不应该使用静态信息卸载任何类。
因此,我期望一个大致与我们相似的模式,在应用程序运行时,该模式会进入各个区域并引用新类,因此加载的类的数量会不断增加。
但是,两件事对我来说似乎很奇怪:
- 为什么如此线性?
- 为什么不平稳?
我希望它会呈上升趋势,但会摆动,然后逐渐减小,因为JVM已经加载了程序引用的大多数类。我的意思是,在大多数应用程序中都引用了有限数量的类。
我们是否以某种方式动态地动态创建新类?
我建议通过同一调试器运行一个更简单的测试应用程序,以获取基准案例。然后,我们可以考虑实现自己的ClassLoader,以吐出一些调试信息,或者也许有一个工具可以报告该信息。
我们需要弄清楚正在加载的这些类是什么。
我们可能会发现一些热点标志可用于了解这种行为,例如:
- -XX:+ TraceClassLoading
- -XX:+ TraceClass卸载
这是一个很好的参考:
http://java.sun.com/javase/technologies/hotspot/vmoptions.jsp
Are you dynamically creating new classes on the fly somehow?
谢谢你的帮助。我发现了问题所在。在我的一个课程中,我使用Jaxb创建XML字符串。为此,JAXB使用反射来创建一个新类。
JAXBContext context = JAXBContext.newInstance(this.getClass());
因此,尽管JAXBContext并没有在堆中说出来,但是已经加载了这些类。
我再次运行了程序,并且看到了正常的平稳状态。
我敢打赌,问题与字节码生成有关。
许多库使用CGLib,BCEL,Javasist或者Janino在运行时为新类生成字节码,然后从受控类加载器中加载它们。释放这些类的唯一方法是释放对类加载器的所有引用。
由于类加载器由每个类保存,因此这也意味着我们也不应释放对所有类的引用[1]。我们可以使用像样的探查器来捕获它们(我使用Yourkit搜索具有相同保留大小的多个classloader实例)
一个陷阱是,JVM默认情况下不会卸载类(原因是向后兼容,人们认为(错误地)静态初始化器仅执行一次。事实是每次加载类时它们都会执行。)卸载时,应使用以下选项传递一些信息:
-XX:+CMSPermGenSweepingEnabled -XX:+CMSClassUnloadingEnabled
(已通过JDK 1.5测试)
即使这样,生成过多的字节码也不是一个好主意,因此我建议我们在代码中查找罪魁祸首并缓存生成的类。罪犯经常是脚本语言,动态代理(包括由应用程序服务器生成的代理)或者庞大的Hibernate模型(在这种情况下,我们可以增加自己的权限)。
也可以看看:
- http://blogs.oracle.com/watt/resource/jvm-options-list.html
- http://blogs.oracle.com/jonthecollector/entry/presenting_the_permanent_generation
- http://forums.sun.com/thread.jspa?messageID=2833028
使用Eclipse Memory Analyzer检查重复的类和内存泄漏。可能会发生同一个类被多次加载的情况。
问候,
马库斯