JVM工作原理之三:JVM类加载器

来源:互联网 发布:打印机记录软件 编辑:程序博客网 时间:2024/06/08 13:04

一. 基本概念

类加载器是用来把类 class 装载入 JVM 

 

Java 运行时会产生三个 ClassLoader

 

Bootstrap ClassLoader(C++ 编写 )            用来加载核心类库,如 java.lang.* 

ExtClassLoader                         用来加载 ext 目录下或者 ext.dir 指定的目录下的类库。

AppClassLoader                        用来加载 CLASSPATH 下的类库及类

 

    其中,ExtClassLoader和AppClassLoader也是由Bootstrap ClassLoader加载的。我们也可以继承ClassLoader,实现自己的ClassLoader

 

 

二. 双亲委托模型

 

    更好的保证 JAVA 平台的安全。在此模型下,当一个装载器被请求加载某个类时,先委托自己的 parent 去装载,如果 parent 能装载,则返回这个类对应的 Class 对象,否则,递归委托给父类的父类装载。

 

    在此模型下,用户自定义的类装载器,不可能装载应该由父亲装载的可靠类,从而防止不可靠甚至恶意的代码代替本应该由父亲装载器装载的可靠代码。

 

 

三. 命名空间

 

    假设我们有如下结构:

                       Loader1( 装载 Class1)

                                        

Loader2 ( 装载 Class2)

                         ↑                        

Loader3( 装载 Class3)             Loader4( 装载 Class4)

其中, Loader1 实际装载了 Class1  Loader 实际装载了 Class2 ,其余类似。

 

这里我们明确 2 个概念:

    定义类装载器 :实际装载类的类装载器。比如上例中的 Class1 的定义类装载器就是 Loader1  Class3 的定义类装载器就是 Loader3 

    初始类装载器: 任何被要求装载某个类型,并且能够返回该类型的 Class 的类装载器,都称为改类型的初始类装载器。比如上例中, Class1 的初始装载器有 Loader3,Loader4,Loader2,Loader1    所以,从定义类装载器往下的所有子装载器,都是该类型的初始类装载器,包括定义类装载器。

 

   每个 ClassLoader 都有自己的命名空间,命名空间由所有以此装载器为初始类装载器的类组成,见下表:

类加载器

命名空间

Loader1

Class1

Loader2

Class1 Class2

Loader3

Class1 Class2 Class3

Loader4

Class1 Class2 Class4

 

        ClassLoader 在调用 loadClass 之前,总会先检查当前的命名空间(内部列表),如果 ClassLoader 是这个类型的初始类装载器,就会返回表示这个类型的 Class 实例。这样,虚拟机永远不会在同一个 ClassLoader 上装载同一个类型 2 次。

 

        不同命名空间的两个类是不可见的 (如,上例中的 Class3  Class4  ,但只要得到类所对应的Class对象的reference,还是可以访问另一命名空间的类。

 

 

 

四. 运行时包

 

    由同一个 ClassLoader 定义装载的属于相同包的类,组成了运行时包,决定两个类是不是属于同一个运行时包,不仅要看包名是否相同,还要看是否是由同一个 ClassLoader 加载的。 只有属于同一个运行时包的类才能互相访问包可见的类和成员。

 

      这样的限制避免了用户自己的代码冒充核心类库的类访问核心类库包可见成员的情况。假设用户自己定义了一个类 java.lang.xxx ,并用自定义的 ClassLoader 装载,由于 Java.lang.*  java.lang.xxx 是由不同的装载器装载,属于不同的运行时包,所以 java.lang.xxx 不能访问核心类库 java.lang 中类的包可见成员。

 

综上,命名空间隔并不完全禁止属于不同空间的类的互相访问,而双亲委托加强了 Java 的安全,运行时包增加了对包可见成员的保护。

 

线程上下文类加载器


线程上下文类加载器(context class loader)是从 JDK 1.2 开始引入的。类 java.lang.Thread中的方法 getContextClassLoader()setContextClassLoader(ClassLoader cl)用来获取和设置线程的上下文类加载器。如果没有通过 setContextClassLoader(ClassLoader cl)方法进行设置的话,线程将继承其父线程的上下文类加载器。Java 应用运行的初始线程的上下文类加载器是系统类加载器。在线程中运行的代码可以通过此类加载器来加载类和资源。

前面提到的类加载器的代理模式并不能解决 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 的实现中都会用到。

自身感觉 正是由于在jdk rt.jar内部需要去装载第三方类库并实例化对象从而造成了困局 所以提出了使用线程上下文类加载器的概念(即将系统类加载器放到当前线程的上下文中,当线程执行到jdk.rt.jar中代码需要加载第三方类的时候,就从当前线程上下文中取出系统类加载器来加载第三方类),但如果jdk rt.jar针对接口编程而不是针对对象编程 就没有这个问题了。对于多个第三方实现的使用,应该是jdk暴露接口接收接口引用,而具体的对象由使用方(编程人员)指定并实例化传给jdk接口就可以了。


六. 实现自己的ClassLoader

 

我们也可以实现自己的ClassLoader,通过继承ClassLoader类,并重写findClass方法。例:

 

[java] view plaincopy
  1. import java.io.ByteArrayOutputStream;  
  2. import java.io.File;  
  3. import java.io.FileInputStream;  
  4. import java.io.IOException;  
  5.   
  6. public class MyClassLoader extends ClassLoader {     
  7.     
  8.     public Class findClass(String name) {     
  9.         byte[] data = loadClassData(name);     
  10.         return defineClass(name, data, 0, data.length);     
  11.     }     
  12.     
  13.     public byte[] loadClassData(String name) {     
  14.         FileInputStream fis = null;     
  15.         byte[] data = null;     
  16.         try {     
  17.             fis = new FileInputStream(new File("E:/home/" + name.replace(".""/") + ".class"));     
  18.             ByteArrayOutputStream out = new ByteArrayOutputStream();     
  19.             int ch = 0;     
0 0
原创粉丝点击