现在的位置: 首页 > 综合 > 正文

Java 类加载体系与ContextClassLoader

2018年05月11日 ⁄ 综合 ⁄ 共 2545字 ⁄ 字号 评论关闭

 va是非常简单精巧的语言,背后的基本原来也很简单,总的说来有两点:

1 . JVM的内存管理,理解了这个,有关对象的问题都能解决。比如线程安全问题,内存泄露问题等。

2.JVM的类加载体系,理解了这个,有关jar包的配置问题,包括各种appServer的配置,应用的发布问题都能解决。

 

有关JVM的内存管理,只要理解了以上的图,基本上就能理解得八九不离十。本文档主要讲解JVM的类加载体系,在我们的平常开发中,大多使用了默认的类加载器,不需要深入理解类加载原理。但如果你不仅仅满足于平时的开发,想深入了解一些底层原理,或者想阅读一些开源软件的源代码,如tomcat,jdbc等的实现,则有必要深入研究类加载的原理。本文档就是为想成为java高手的人准备的。

 

本文不准备对基本的类加载原理做过多的描述,因为此类文章在网上多牛毛,不了解类加载原理的同学请参考JAVA面向对象编程 一书的第十章(类的声明周期)或者java深度历险。下面摘取几个主要的图来说明大致的原理。

图1是java的类加载体系,有BootStrap , Ext , App和用户自定义加载器,加载类的时候采用双亲委托机制。特别需要注意的是:加载器ClassLoader的父子关系和继承关系无关,也和加载器是有哪个加载器加载的无关,而是在创建加载器时指定的父加载器有关,即是由人工指定的。比如说要确定加载器A的父加载器,仅仅是由创建对象A时传进去的父加载器决定,而不管A的类型是什么,也不管A是由哪个加载器加载的。

加载Class的默认规则比较简单,有两个:

1,  双亲委托机制。

2,  同一个加载器:类A引用到类B,则由类A的加载器去加载类B,保证引用到的类由同一个加载器加载。

 

图2是加载器,Class对象,和类在内存中的组织形式。加载器,Class对象都是实例对象,都是存在heap区。而类的相关信息则存在方法区,也就是Perm 区。(BTY:如果方法区满了,就会抛出java.lang.Out of Memory Perm Gem Space Error的异常)。

图3是java程序的执行过程,可以看到执行时是先加载如整个类加载体系,然后用此体系加载我们要执行的类,然后运行main方法。

图四是jvm加载类的三种方法,包括隐式的new,和显式的Class.forName()和ClassLoader.loadCLass().

 

图1

图2

图三

图四

在大部分情况下,jvm的这个默认类加载体系已经能满足大部分情况的使用了。那为什么还需要ContextClassLoader呢?这其实是因为加载Class的默认规则在某些情况下不能满足要求。比如jdk中的jdbc API 和具体数据库厂商的实现类SPI的类加载问题。在jdbc API的类是由BootStrap加载的,那么如果在jdbc API需要用到spi的实现类时,根据默认规则2,则实现类也会由BootStrap加载,但是spi实现类却没法由BootStrap加载,只能由Ext或者App加载,如何解决这个问题?牛人们就想出了ContextClassLoader的方法。

具体的实现过程如下:在类Thread定义一个属性classLoader,用来供用户保存一个classLoader(默认是App),并公开setter和getter方法,使得此线程的任何语句都可以得到此classLoader,然后用它来加载类,以此来打破默认规则2。说白了,ContextClassLoader就是Thread的一个属性而已,没什么复杂的。只不过这个属性和底层的加载体系联系紧密,使得很多人以为有什么高深的原理在里面。明白了这一点,ContextClassLoader也就没什么神秘的了。

那么我们看看ContextClassLoader是如何解决上面的问题的呢?以jdbc为例,当DriverManager需要加载SPI中的实现类是,可以获取ContextClassLoader(默认是App),然后用此classLoader来加载spi中的类。很简单的过程。当然不使用ContextClassLoader,自己找个地方把classLoader保存起来,在其他地方能得到此classLoader就可以。Tomcat就是这么做的。比如StandardContext是由Common加载的,而StandardContext要用到项目下的类时怎么办,显然不能用Common来加载,而只能用WebAppClassLoader来加载,怎么办?当然可以采用ContextClassLoader的方式来解决,但是tomcat不是这样解决的,而是为每个App创建一个Loader,里面保存着此WebApp的ClassLoader。需要加载WebApp下的类时,就取出ClassLoader来使用,原理和ContextClassLoader是一样的。至于tomcat为什么要这么做呢?因为tomcat中有关类加载的问题,是由一个main线程来处理的,而并没有为每个WebApp单独创建一个线程,故没办法用ContextClassLoader的方式来解决,而是用自己的属性来解决。

 

Tomcat6的类加载器体系,来自:http://tomcat.apache.org/tomcat-6.0-doc/class-loader-howto.html

 

 

 

既然规则2已经被打破了,那么规则1:双亲委托机制能被打破吗?规则1是因为安全问题而设置的,如果我自己能控制类加载的安全问题,是否可以违反规则1呢?答案是肯定的。同样的Tomcat也打破了此规则。Tomcat中WebAppClassLoader加载项目的类时,可以决定是否先在项目本地的路径中查找class文件。而不是自动使用父加载器Common在tomcat下的common目录查找。这是怎么实现的呢?

要回答这个问题,就要先明白双亲委托机制是如何实现的。只有明白了如何实现,才有办法改变它。双亲委托机制是在类ClassLoader的loadClass里面实现的。那么继承ClassLoader的WebAppClassLoader,只要覆盖loadClass方法,实现自己的加载策略,即可避开双亲委托机制。

抱歉!评论已关闭.