classloader

一、基本介绍:

Java中所有的类都是通过classloader加载的,JVM在运行时会产生三个ClassLoader,Bootstrap ClassLoader、Extension ClassLoader和AppClassLoader.其中,Bootstrap是用C++编写的,我们在Java中看不到它,是null。它用来加载 核心类库,在JVM源代码中这样写道:

static const char classpathFormat[] =
    "%/lib/rt.jar:"
    "%/lib/i18n.jar:"
    "%/lib/sunrsasign.jar:"
    "%/lib/jsse.jar:"
    "%/lib/jce.jar:"
    "%/lib/charsets.jar:"
    "%/classes";
   JVM启动的时候就自动加载了,并且在运行过程中根本不能修改Bootstrap加载路径。Extension ClassLoader用来加载扩展类,即/lib/ext中的类。最后AppClassLoader才是加载Classpath的。

ClassLoader加载类用的是委托模型。即先让Parent类(而不是Super,不是继承关系)寻找,Parent找不到才自己找。三者的关系为:AppClassLoader的Parent是ExtClassLoader,而 ExtClassLoader的Parent为Bootstrap ClassLoader。加载一个类时,首先BootStrap先进行寻找,找不到再由ExtClassLoader寻找,最后才是 AppClassLoader。下图为类加载器层次结构:

classloader_第1张图片

为什么要设计的这么复杂呢?其中一个重要原因就是安全性。 比如在Applet中,如果编写了一个java.lang.String类并具有破坏性。假如不采用这种委托机制,就会将这个具有破坏性的String加 载到了用户机器上,导致破坏用户安全。但采用这种委托机制则不会出现这种情况。因为要加载java.lang.String类时,系统最终会由 Bootstrap进行加载,这个具有破坏性的String永远没有机会加载。

 classloader_第2张图片

classloader_第3张图片


在上面的代码中,我们可以清晰的看见,我们调用一个ClassLoader加载程序的时候,这个ClassLoader会先调用设置好的parentClassLoader来加载这个类,如果parent是null的话,则默认为BootClassLoader类,只有在parent没有找的情况下,自己才会加载,这就避免我们重写一些系统类,来破坏系统的安全;

classloader_第4张图片

classloader_第5张图片

   类与它所依赖的类的classloader机制:如果一个类是由某个classloader加载,那么这个类依赖的类(非显式的通过某个classloader加载)必须也由该classloader或其父classloader加载,无视子classloader。比如A类依赖B类,你在A类的某个方法中new B(); 不可能A类由BootstrapLoader加载,而B 类由AppClassLoader或者一个A类实现了C 接口,不可能A类由 BootstrapLoader加载,而C接口由 AppClassLoader

呵呵,没这么简单。比如JDBC是核心类库,而各个数据库的JDBC驱动则 是扩展类库或在classpath中定义的。所以JDBC由Bootstrap ClassLoader加载,而驱动要由AppClassLoader加载。等等,问题来了,Bootstrap不会请求AppClassLoader加 载类啊。那么,他们怎么实现的呢?我就涉及到一个Context ClassLoader的问题,调用Thread.getContextClassLoader。

二、线程上下文类加载器

    类加载器的代理模式并不能解决Java 应用开发中会遇到的类加载器的全部问题。Java 提供了很多服务提供者接口(Service Provider Interface,SPI),允许第三方为这些接口提供实现。常见的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。这些 SPI 的接口由 Java 核心库来提供,如 JAXP 的 SPI 接口定义包含在javax.xml.parsers 包中。这些 SPI 的实现代码很可能是作为 Java 应用所依赖的 jar 包被包含进来,可以通过类路径(CLASSPATH)来找到,如实现了 JAXP SPI 的Apache Xerces 所包含的jar 包。SPI 接口中的代码经常需要加载具体的实现类。如JAXP 中的javax.xml.parsers.DocumentBuilderFactory 类中的newInstance() 方法用来生成一个新的DocumentBuilderFactory 的实例。这里的实例的真正的类是继承自javax.xml.parsers.DocumentBuilderFactory , 由 SPI 的实现所提供的。如在Apache Xerces 中,实现的类是org.apache.xerces.jaxp.DocumentBuilderFactoryImpl。而问题在于,SPI 的接口是 Java 核心库的一部分,是由引导类加载器来加载的;SPI 实现的 Java 类一般是由系统类加载器来加载的。引导类加载器是无法找到 SPI 的实现类的,因为它只加载 Java 的核心库。它也不能代理给系统类加载器,因为它是系统类加载器的祖先类加载器。也就是说,类加载器的代理模式无法解决这个问题。

    线程上下文类加载器正好解决了这个问题。如果不做任何的设置,Java 应用的线程的上下文类加载器默认就是系统上下文类加载器。在 SPI 接口的代码中使用线程上下文类加载器,就可以成功的加载到 SPI 实现的类。线程上下文类加载器在很多 SPI 的实现中都会用到。

例如JDBC中、部分源代码片段如下:

DriverManager.getConnection(url,user, psw);方法:

ClassLoadercallerCL = DriverManager.getCallerClassLoader();

getConnection(url,info,callerCL)

……

if(callerCL== null){

 callerCL= Thread.currentThread().getContextClassLoader();

……

代码意思是如果DriverManager类的类加载器为空的话,就使用当前线程的类加载器。仔细想想,DriverManager在rt.jar包中,它是由JDK的启动类加载器加载的,而启动类加载器是C编写的,所以取得的都是空,再者,使用当前线程类加载器的话,那么交由程序编写者来保证能够加载驱动类。而不至于驱动器类无法加载。

总之classloader默认的委托模型使得处于上层的classloader中的类无法访问处于下层classloader中的类,这带来了安全性的同时也失去了灵活性,幸好 可以使用Thread.currentThread().getContextClassLoader().

注意:但在JVM中一个类用其全名和一个加载类ClassLoader的实例作为唯一标识。在设计单例模式时要注意考虑到多个类加载器的情况。

 

 

参考资料:

http://www.cnblogs.com/realviv/articles/1906110.html

http://www.docin.com/p-151359546.html

http://jiajun.iteye.com/blog/608564

 

 

三、举例:

 

 classloader_第6张图片

classloader_第7张图片

Get/Set性能测试:



你可能感兴趣的:(java,apache,jvm,ClassLoader,jdbc,extension)