Java_ClassLoader内存溢出-从tomcat的reload提及

原文连接:http://nius.me/classloader-memory-leak/html

对于j2ee项目,一直使用eclipse的wtp,每次修改代码后,能够自动热部署。简单的项目wtp彷佛没什么问题,但一旦项目代码稍微多一点,就很容易出现各类莫名其妙掉挂的现象,不得不整个重启tomcat服务器,这个时候就很痛苦了。java

因而,我换用了maven的jetty插件,启动一个轻量级的jetty服务器,这下热部署彷佛没那么多问题了!可是jetty彷佛在热部署几回以后,也仍然会崩溃!这是什么状况!tomcat和jetty到底发生了什么?jetty的崩溃最常看见的异常就是OutOfMemoryException,Perm区的内存占满了git

short version

不要慌!为了节省时间,先上解决方案:classloader-leak-preventiongithub

  1. 在maven中添加以下配置:
<dependency>
  <groupId>se.jiderhamn</groupId>
  <artifactId>classloader-leak-prevention</artifactId>
  <version>1.9.3</version>
</dependency>
  1. 在web.xml__顶部__添加一个listener
<listener>
    <listener-class>
        se.jiderhamn.classloader.leak.prevention.ClassLoaderLeakPreventor
    </listener-class>
</listener>
  1. 去西湖玩一天

long version

看看Perm区到底都是些啥

今天实在忍不了了,上Visual VM,监视一下Perm区的状况。

果真,reload几回以后,Perm的使用量蹭蹭的往上涨。看看Perm区都是啥

怎么这么多char[]呀,好像也看不出什么,仍是dump一下吧。中间翻阅了一些博客,以为应该是class loader的问题。oql查询一下。(这里由于是jetty,因此查询了jetty的WebAppClassLoader,若是使用tomcat或者其余容器,应该有对应的Loader,能够经过Saved Queries->PermGen Analysis->ClassLoader Types看一下有哪些ClassLoader)web


上图是刚启动的时候,WebAppClassLoader只有一个实例。通过两次reload,嘿,问题来了。以下图:sql

竟然出现了3个WebAppClassLoader实例,前两个Loader都没有销毁!哟呵呵,gc销不掉,一会就进Perm区了,而后多几回reload,Perm区再大也都撑满了。tomcat

发生了什么,gc不靠谱?

首先,咱们回忆一下gc的运做过程。经过minor gc,清理eden区(eden generation)的没有被引用的对象,活下来的进入suvivor区,接下来的minor gc会让对象在两个suvivor里面倒腾倒腾,挺过几回的进入old区,这里面进行的就是full gc了,耗时长,若是old区还挺了好几回,就会进入Perm区。Perm里面发生的也是full gc.服务器

一个普通对象,只要没有引用了,就必定会在某一次gc被回收。那么ClassLoader进入了Perm,说明在reload的时候,虽然程序取消了对Classloader的直接引用,可是仍然有其余对象间接的引用了ClassLoader。其实若是单纯的仅仅是ClassLoader一个对象,也就罢了,可是ClassLoader并非一个普通的对象。less

任何一个Java类,都是经过某个ClassLoader加载的。经过这个ClassLoader加载的类的实例,会保存这个类对象的引用,也就是MyClass.class这种。而类对象,会保留这个ClassLoader的引用。反过来,在ClassLoader中,也会保持对这个类对象的引用。(注意区分类对象MyClass.class,不是这个类的实例。好吧若是仍是混淆了,我也不知道该怎么说清楚了)。关键在于,ClassLoader和类对象之间是双向引用。eclipse

双向引用有什么问题嘛?通常状况下没有问题。由于若是ClassLoader的外界引用,和具体类对象的外界引用都消失了,那么这两个对象都不可达了,都会被gc。可是在一些状况下,类对象可能不单单被这个类的实例保存,还可能被其余对象保存!若是这个对象是其余OtherClassLoader加载的呢?那意味着,若是这个对象不回收,那么其引用的类对象不会被回收,因而ClassLoader不会被回收,因而,全部ClassLoader加载的类对象都不会被回收!WebAppClassLoader会加载多少个类?若是你刚好使用的是Spring、Hibernate这种你们伙,嚯嚯。若是对此很感兴趣,这里有一篇写的很详细的:Anatomy of a PermGen Memory Leak

怎么解决

你并不知道何时会出现某个外部对象会引用到类对象。因此解决问题的思路是,换一个ClassLoader。一开始的解决方案classloader-leak-prevention就是依赖这个思路的。核心代码以下:

      // Switch to system classloader in before we load/call some JRE stuff that will cause 
      // the current classloader to be available for garbage collection
      Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader());

      try {
        java.awt.Toolkit.getDefaultToolkit(); // Will start a Thread
      }
      catch (Throwable t) {
        error(t);
        warn("Consider adding -Djava.awt.headless=true to your JVM parameters");
      }

      java.security.Security.getProviders();
      
      java.sql.DriverManager.getDrivers(); // Load initial drivers using system classloader

      javax.imageio.ImageIO.getCacheDirectory(); // Will call sun.awt.AppContext.getAppContext()

让这一段代码运行在servlet初始化以前,在全部的listener以前。

 

 

下面是本身翻阅的一些资料:

英文的资料:http://java.jiderhamn.se/2012/03/04/classloader-leaks-vi-this-means-war-leak-prevention-library/      (在此连接可找到最新的maven地址以及源码地址以及非maven得jar)

Git源码地址:https://github.com/mjiderhamn/classloader-leak-prevention

相关文章
相关标签/搜索