1 什么是ClassLoader?
- 1 大家都知道,当我们写好一个Java程序之后,不是管是CS还是BS应用,都是由若干个.class文件组织而成的一个完整的Java应用程序,当程序在运行时,即会调用该程序的一个入口函数来调用系统的相关功能,而这些功能都被封装在不同的class文件当中,所以经常要从这个class文件中要调用另外一个class文件中的方法,如果另外一个文件不存在,则会引发系统异常。
- 2 而程序在启动的时候,并不会一次性加载程序所要用的所有class文件,而是根据程序的需要,通过Java的类加载机制(ClassLoader)来动态加载某个class文件到内存当中的,从而只有class文件被载入到了内存之后,才能被其它class所引用。所以ClassLoader就是用来动态加载class文件到内存当中用的。
2 Java提供的三个默认ClassLoader
- 1 BootStrap ClassLoader:称为启动类加载器,是Java类加载层次中最顶层的类加载器,负责加载JDK中的核心类库,如:rt.jar、resources.jar、charsets.jar等,可通过查找sun.boot.class.path这个系统属性,得知该类加载器从哪些地方加载了相关的jar或class文件。
System.out.println(System.getProperty("sun.boot.class.path"));
输出结果为:
C:\Program Files\Java\jdk1.6.0_22\jre\lib\resources.jar;
C:\Program Files\Java\jdk1.6.0_22\jre\lib\rt.jar;
C:\Program Files\Java\jdk1.6.0_22\jre\lib\sunrsasign.jar;
C:\Program Files\Java\jdk1.6.0_22\jre\lib\jsse.jar;
C:\Program Files\Java\jdk1.6.0_22\jre\lib\jce.jar;
C:\Program Files\Java\jdk1.6.0_22\jre\lib\charsets.jar;
C:\Program Files\Java\jdk1.6.0_22\jre\classes
- 2 Extension ClassLoader:称为扩展类加载器,负责加载Java的扩展类库,默认加载JAVA_HOME/jre/lib/ext/目下的所有jar。
- 3 App ClassLoader:称为系统类加载器,负责加载应用程序classpath目录下的所有jar和class文件。
- 4 总结
Extension ClassLoader和App ClassLoader也继承自java.lang.ClassLoader类,但是Bootstrap ClassLoader不继承自java.lang.ClassLoader类,因为它不是一个普通的Java类,底层由C++编写,已嵌入到了JVM内核当中,当JVM启动后,Bootstrap ClassLoader也随着启动,负责加载完核心类库后,并构造Extension ClassLoader和App ClassLoader类加载器。
3 ClassLoader加载类的原理
- 1 原理简介
1)ClassLoader使用的是双亲委托模型来搜索类的,每个ClassLoader实例都有一个父类加载器的引用(不是继承的关系,是一个包含的关系)。
2)类加载器的包含关系如下:
自定义类加载器————>系统类加载器————>扩展类加载器————>根加载器
3)当一个自定义ClassLoader实例需要加载某个类时,先从自己的命名空间里查找是否已加载该类,如果已加载,则直接返回代表该类的Class对象的引用。
4)如果没加载,才委托其父类加载器进行加载。其父类加载器命名空间没有在,则一层层向上委托,直到根加载器。然后由根加载器开始从上到下加载该类。一旦加载到则将这个找到的类生成一个类的定义,并将它加载到内存当中,最后返回这个类在内存中的Class实例对象的引用。
5)如果所有加载器都没有加载到这个类时,则抛出ClassNotFoundException异常。 - 2 为什么要使用双亲委托这种模型呢?
1)这样可以避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。
2)考虑到安全因素,我们试想一下,如果不使用这种委托模式,那我们就可以随时使用自定义的String来动态替代java核心api中定义的类型,这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为String已经在启动时就被引导类加载器(Bootstrcp ClassLoader)加载,所以用户自定义的ClassLoader永远也无法加载一个自己写的String,除非你改变JDK中ClassLoader搜索类的默认算法。 - 3 JVM在搜索类的时候,又是如何判定两个class是相同的呢?
1)JVM在判定两个class是否相同时,不仅要判断两个类名是否相同,而且要判断是否由同一个类加载器实例加载的。只有两者同时满足的情况下,JVM才认为这两个class是相同的。就算两个class是同一份class字节码,如果被两个不同的ClassLoader实例所加载,JVM也会认为它们是两个不同class。
2)比如网络上的一个Java类,javac编译之后生成字节码文件,同一个自定义类加载器的两个实例同时读取了这个字节码文件,并分别定义出了java.lang.Class实例来表示这个类,对于JVM来说,它们是两个不同的实例对象,但它们确实是同一份字节码文件,如果试图将这个Class实例生成具体的对象进行转换时,就会抛运行时异常java.lang.ClassCaseException,提示这是两个不同的类型。
4 定义自已的ClassLoader
- 1 既然JVM已经提供了默认的类加载器,为什么还要定义自已的类加载器呢?
因为Java中提供的默认ClassLoader,只加载指定目录下的jar和class,如果我们想加载其它位置的类或jar时,比如:我要加载网络上的一个class文件,通过动态加载到内存之后,要调用这个类中的方法实现我的业务逻辑。在这样的情况下,默认的ClassLoader就不能满足我们的需求了,所以需要定义自己的ClassLoader。 - 2 定义自已的类加载器分为两步:
1)继承java.lang.ClassLoader
2)重写父类的findClass方法 - 3 父类有那么多方法,为什么偏偏只重写findClass方法?
因为JDK已经在loadClass方法中帮我们实现了ClassLoader搜索类的算法,当在loadClass方法中搜索不到类时,loadClass方法就会调用findClass方法来搜索类,所以我们只需重写该方法即可。 - 4 代码举例
package lwlstudy.test.classloader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class MyClassLoader extends ClassLoader {
private String classPath;
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
public String getClassPath() {
return classPath;
}
public void setClassPath(String classPath) {
this.classPath = classPath;
}
public MyClassLoader(ClassLoader parent) {
super(parent);
}
@Override
protected Class<?> findClass(String className) throws ClassNotFoundException {
byte[] data = this.getData(className);
if (data == null) {
throw new ClassNotFoundException();
} else {
return defineClass(className, data, 0, data.length);
}
}
private byte[] getData(String className) {
String path = classPath + File.separatorChar + className.replace('.', File.separatorChar) + ".class";
InputStream is = null;
ByteArrayOutputStream stream = null;
try {
is = new FileInputStream(new File(path));
stream = new ByteArrayOutputStream();
byte[] buffer = new byte[2048];
int num = 0;
while ((num = is.read(buffer)) != -1) {
stream.write(buffer, 0, num);
}
return stream.toByteArray();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
is.close();
stream.close();
} catch (IOException e2) {
e2.printStackTrace();
}
}
return null;
}
public static void main(String[] args) throws Exception {
MyClassLoader mcl1 = new MyClassLoader("W:\\zz");
Class<?> clazz = mcl1.loadClass("lwlstudy.test.classloader.Dog");
Object obj = clazz.newInstance();
Object obj1 = clazz.newInstance();
System.out.println(obj.getClass().equals(obj1.getClass()));
MyClassLoader mcl2 = new MyClassLoader("W:\\zz");
Class<?> clazz2 = mcl2.loadClass("lwlstudy.test.classloader.Dog");
Object obj2 = clazz2.newInstance();
System.out.println(obj.getClass().equals(obj2.getClass()));
}
}
```
输出结果为:
```java
true
false
```
原因参考:如何判定两个class是相同的呢?
网友评论