一. 基本概念
类加载器是用来把类 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 的安全,运行时包增加了对包可见成员的保护。
五. 实现自己的ClassLoader
我们也可以实现自己的ClassLoader,通过继承ClassLoader类,并重写findClass方法。例:
import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; public class MyClassLoader extends ClassLoader { public Class findClass(String name) { byte[] data = loadClassData(name); return defineClass(name, data, 0, data.length); } public byte[] loadClassData(String name) { FileInputStream fis = null; byte[] data = null; try { fis = new FileInputStream(new File("E:/home/" + name.replace(".", "/") + ".class")); ByteArrayOutputStream out = new ByteArrayOutputStream(); int ch = 0; while ((ch = fis.read()) != -1) { out.write(ch); } data = out.toByteArray(); } catch (IOException e) { e.printStackTrace(); } return data; } }