一个java程序被执行,编译器先将.java文件编译成class文件,然后classloader将class文件加载到jvm内存中来执行应用程序。classloader起到了至关重要的作用,下面我们来看下classloader的分类:
系统类加载器
- Bootstrap ClassLoader
用C/C++代码实现的加载器,用于加载Java虚拟机运行时所需要的系统类,如java.lang.、java.uti.等这些系统类,它们默认在$JAVA_HOME/jre/lib目录中,也可以通过启动Java虚拟机时指定-Xbootclasspath选项,来改变Bootstrap ClassLoader的加载目录。
Java虚拟机的启动就是通过 Bootstrap ClassLoader创建一个初始类来完成的。由于Bootstrap ClassLoader是使用C/C++语言实现的, 所以该加载器不能被Java代码访问到。需要注意的是Bootstrap ClassLoader并不继承java.lang.ClassLoader。 - Extensions ClassLoader
用于加载 Java 的拓展类 ,拓展类的jar包一般会放在$JAVA_HOME/jre/lib/ext目录下,用来提供除了系统类之外的额外功能。也可以通过-Djava.ext.dirs选项添加和修改Extensions ClassLoader加载的路径。 - App ClassLoader
负责加载当前应用程序Classpath目录下的所有jar和Class文件。也可以加载通过-Djava.class.path选项所指定的目录下的jar和Class文件。
自定义类加载器
就是我们自己写一个类继承ClassLoader,然后重写findClass方法加载指定目录下的class文件
classLoader继承关系
我们知道系统所提供的类加载器有3种类型,但是系统提供的ClassLoader相关类却不只3个。另外,AppClassLoader的父类加载器为ExtClassLoader,并不代表AppClassLoader继承自ExtClassLoader,ClassLoader的继承关系如下所示。
image.png
可以看到上图中共有5个ClassLoader相关类,下面简单对它们进行介绍:
- ClassLoader是一个抽象类,其中定义了ClassLoader的主要功能。
- SecureClassLoader继承了抽象类ClassLoader,但SecureClassLoader并不是ClassLoader的实现类,而是拓展了ClassLoader类加入了权限方面的功能,加强了ClassLoader的安全性。
- URLClassLoader继承自SecureClassLoader,用来通过URl路径从jar文件和文件夹中加载类和资源。
- ExtClassLoader和AppClassLoader都继承自URLClassLoader,它们都是Launcher 的内部类,Launcher 是Java虚拟机的入口应用,ExtClassLoader和AppClassLoader都是在Launcher中进行初始化的。
双亲委派机制
类加载器查找Class所采用的是双亲委托模式,所谓双亲委托模式就是首先判断该Class是否已经加载,如果没有则不是自身去查找而是委托给父加载器进行查找,这样依次的进行递归,直到委托到最顶层的Bootstrap ClassLoader,如果Bootstrap ClassLoader找到了该Class,就会直接返回,如果没找到,则继续依次向下查找,如果还没找到则最后会交由自身去查找。
image.png这样讲可能会有些抽象,来直接看源码:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);//第一步
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);////第二步
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);
}
}
return c;
}
protected final Class<?> findLoadedClass(String name) {
ClassLoader loader;
if (this == BootClassLoader.getInstance())
loader = null;
else
loader = this;
return VMClassLoader.findLoadedClass(loader, name);
}
- findLoadedClass 在当前classloader中查找是否加载过该class,如果没加载过就交给父加载器进行加载
- parent.loadClass(name, false),父加载器存在的话就交给父加载器进行加载,不存在的就直接交由系统跟类加载器家在。同样父加载器也是在当前的classloader中查找是否加载过该class,如果没加载过就交给父加载器进行加载。不断重复第一步,递归调用。
- 一直委托到Bootstrap ClassLoader,如果Bootstrap ClassLoader在缓存中还没有查找到Class文件,则在自己的规定路径$JAVA_HOME/jre/libr中或者-Xbootclasspath选项指定路径的jar包中进行查找,如果找到则返回该Class,如果没有则交给子加载器Extensions ClassLoader。
- Extensions ClassLoader查找$JAVA_HOME/jre/lib/ext目录下或者-Djava.ext.dirs选项指定目录下的jar包,如果找到就返回,找不到则交给App ClassLoader。
- App ClassLoade查找Classpath目录下或者-Djava.class.path选项所指定的目录下的jar包和Class文件,如果找到就返回,找不到交给我们自定义的类加载器,如果还找不到则抛出异常
两个过程,先从下到上查询是否有类加载器加载过该class,然后从上到下询问类加载器是否可以加载该class
双亲委托模式的好处
采取双亲委托模式主要有两点好处:
- 避免重复加载,如果已经加载过一次Class,就不需要再次加载,而是先从缓存中直接读取。
- 更加安全,如果不使用双亲委托模式,就可以自定义一个String类来替代系统的String类,这显然会造成安全隐患,采用双亲委托模式会使得系统的String类在Java虚拟机启动时就被加载,也就无法自定义String类来替代系统的String类,除非我们修改
类加载器搜索类的默认算法。还有一点,只有两个类名一致并且被同一个类加载器加载的类,Java虚拟机才会认为它们是同一个类,想要骗过Java虚拟机显然不会那么容易
- 更加安全,如果不使用双亲委托模式,就可以自定义一个String类来替代系统的String类,这显然会造成安全隐患,采用双亲委托模式会使得系统的String类在Java虚拟机启动时就被加载,也就无法自定义String类来替代系统的String类,除非我们修改
网友评论