常见的内存泄漏及解决方法

JVM内存泄漏的原因

所谓JVM内存泄漏,就是在JVM做GC操作时,我们认为本来应该”没有用”的对象,没有被JVM回收掉,其主要原因是因为从JVM的GC ROOT出发,存在一条路径可以寻找到该对象。

常见的GC ROOT主要有以下几种:

  1. System Class - 由系统类加载器(system class loader)加载的对象
  2. Thread - 运行中的线程对象(经常导致内存泄漏的原因,由于Thread对象中持有一个加载该线程类的ClassLoader对象,如果该线程不终止–>ClassLoader对象无法释放–>方法区中该ClassLoader加载的类无法释放–>最终有可能导致方法区内存溢出)
  3. Busy Monitor - 用于监控的对象
  4. Native Stack - 本地方法栈引用的对象?

1. MySQL驱动

如果应用中使用了MySQL 5.1.24+的JDBC驱动的话,在不停止应用server,重新部署应用的时候,会发现JVM的Pergem区有很多内存不能回收,通过VisualVM等工具,可以看到有一个名为Abandoned connection cleanup thread的线程还在后台跑,导致应用的ClassLoader无法释放。

解决方法1: 创建一个类似以下代码的Listener

1
2
3
4
5
6
7
8
9
10
11
12
public class MysqlConnectionClearupListener implements ServletContextListener {
public void contextDestroyed(ServletContextEvent arg0) {
try {
AbandonedConnectionClearupThread.shutdown();
} catch (...) {
...
}
}
...
}

然后在web.xml里配置该Listener

1
2
3
<listener>
<listener-class>MysqlConnectionClearupListener</listener-class>
</listener>

缺点: 该方法需要在应用中引入jdbc的驱动包,不太优雅,而且由于JDBC驱动的注册是JVM级别的,如果该server上有其他应用也使用MySQL的话,有可能产生其他问题。

解决方法2: 由应用服务器负责加载MySQL的驱动

将MySQL的jdbc驱动包放到应用服务器的classpath,例如tomcat中的lib目录,由应用服务器的ClassLoader来加载,并启动Abandoned connection cleanup thread线程,这样应用的ClassLoader就无需对MySQL的类进行装载和卸载。

2. commons-pool

使用commons-pool组件,如果timeBetweenEvictionRunsMillis属性的值设为>0的值,则会在后台启动一个名为common-pool-EvictionTimer的线程,如果在应用卸载时(JVM没有重启),没有调用pool的close方法,则该线程不会停止,导致应用ClassLoader无法回收,从而导致方法区内存泄漏。