类加载器内存泄露与tomcat自定义加载器

1 类加载器内存泄露

每个对象都持有对它的Class引用,也就持有对它的类加载器的引用。相反的,每个类加载器也持有对它加载过的类的引用,保存在Class的静态字段中。如下图所示(图片源自其他博客)

这就意味着如果一个类的加载器发生了内存泄露,那么与之关联的类和所有的静态字段都发生了泄漏。这意味着一些缓存状态、单例以及配置和状态信息也就暴露了出去。即使你的程序没有保存这些信息,但并不意味着你使用的框架没有,因为他们的jar包也在server 的classpath中,由此来看这是非常危险的。

2 Tomcat的类加载模型

对于运行在 Java EE容器中的 Web 应用来说,类加载器的实现方式与一般的 Java 应用有所不同。不同的 Web 容器的实现方式也会有所不同。以 Apache Tomcat 来说,每个 Web 应用都有一个对应的类加载器实例。该类加载器也使用代理模式,所不同的是它是首先尝试去加载某个类,如果找不到再代理给父类加载器。这与一般类加载器的顺序是相反的。这是 Java Servlet 规范中的推荐做法,其目的是使得 Web 应用自己的类的优先级高于 Web 容器提供的类。这种代理模式的一个例外是:Java 核心库的类是不在查找范围之内的。这也是为了保证 Java 核心库的类型安全。

2.1 目录合并

以Tomcat8为例,其类加载器的结构图如下所示,这与网上大多数帖子的图稍有差异,其实那是Tomcat5的结构图,在那个图示中,Tomcat定义了CommonClassLoader、CatalinaClassLoader、SharedClassLoader和WebappClassLoader,他们分别负责加载Tomcat目录下的/common/*、/server/*、/shared/*和/WebApp/WEB-INF/*中的Java类库。而在Tomcat6以后/common/*、/server/*、/shared/*三个文件夹合并为/lib目录。

2.2 Tomcat类加载器

  • Bootstrap :这个Bootstrap是一类加载器的统称,由JVM提供的用来加载Java的jar包
  • System:简单的说,这个加载器的主要作用是用于加载$CATALINA_HOME/bin/目录下的三个jar包:bootstrap.jar、tomcat-juli.jar和commons-daemon.jar。由它的位置也可以知道,通过这个classloader加载的所有类,都对tomcat自身的类,以及所有web应用的类可见
  • Common:用来加载tomcat自身的类的加载器。默认加载$CATALINA_HOME/lib/目录下的jar包或为打包的.class文件,而应用程序的依赖jar则不应该放在这个路径下。它的路径定义在$CATALINA_HOME/conf/catalina.properties文件中common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"
  • WebappX :应用程序专属类加载器。Tomcat为每一个部署的Web应用创建一个类加载器,它用于加载/WEB-INF/lib和/WEB-INF/classes内的类文件,它仅对于自己的应用是可见的,而对其他部署在这个Tomcat实例上的Web应用是隔离的。

2.3 tomcat为什么要自定义类加载器

对于Tomcat来说,我认为最重要的就是为每个应用都分配了一个专属类加载器,主要有以下原因:

1 为了不同webapp加载不同版本的jar包:在现在的web应用中,第三方框架的使用随处可见,但是如果两个webapp都使用了同一个jar包但是版本不同,那么就非常有必要进行隔离。我们知道一个classloader实例对同一个class文件仅能加载一次,在加载某class文件一个版本之后,如果再次搜索到同名的class文件是会抛出异常的。这样对于不同版本的jar包加载还是隔离开比较好。

2 当然是为了安全:由本人第一段可以看到,类加载器的内存泄露是致命的。而如果隔离开,则可以保证其他webapp是安全的

3 类的热部署:类加载器之所以能发展到今天,它能实现类的热部署才是王道。对于一个应用来说,如果在不用停机的情况下就可以进行升级显然是最完美的方式。在我们部署一个应用的时候,通常都会设置reload=true。它的实现就是靠类加载器。

3 Tomcat类加载器启动过程

下面这张图基本描述了Tomcat的启动过程



3.1 bootstrap入口

tomcat的启动入口在org.apache.catalina.startup.Bootstrap类中,其main方法如下(仅列出与类加载器相关的逻辑):
(1)Main方法 它是整个Tomcat启动的入口
<div style="text-align: justify;"><span style="font-family: 宋体;">         Bootstrap bootstrap = new Bootstrap();</span></div>            try {
<div style="text-align: justify;"><span style="font-family: 宋体;">            } catch (Throwable t) {</span></div>                bootstrap.init();//初始化入口
<div style="text-align: justify;"><span style="font-family: 宋体;">                t.printStackTrace();</span></div>                handleThrowable(t);
                return;
            }
<div style="text-align: justify;"><span style="font-family: 宋体;">            daemon = bootstrap;</span></div>
(2)init方法

在init方法里面,对各种类加载器进行了初始化,然后利用catalinaLoader加载了Catalina类并初始化Catalina对象,将Catalina的父加载器设置为sharedLoader

/** *初始化各种类加载器  利用catalina类加载器加载Catalina对象 */
     
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值