写在前面
通过本文的学习,你将了解到;
1.什么是类加载器?类加载的作用?
2.类加载的机制是什么样的?
什么是类加载器
类加载器,简称ClassLoader,ClassLoader的主要作用是将class文件加载到jvm中。
但是在jvm启动的时候,并不会一次性加载全部的class文件,class文件的加载是一个动态加载的过程。
class文件
我们在编写代码的时候,比如在eclipse,intelij等ide中写的都是java文件,需要编译成class文件,才能执行
Test.java代码:
public class Test {
public static void main(String[] args) {
System.out.println("abc");
}
}
通过在cmd中执行:
javac Test.java
就会生成Test.class文件
在cmd中执行:
java Test
则可打印出执行结果abc.
class文件是java文件编译后生成的字节码文件,因为jvm并不能识别我们写的源代码java文件,所以需要使用javac编译成java虚拟机可以识别运行的class文件。(其他语言生成的.class文件,java虚拟机也是可以识别运行的)
java中默认的三个类加载器
BootStrap ClassLoader,ExtClassLoader,AppClassLoader
BootStrap ClassLoader是最顶层的类加载器,是底层的一个实现,嵌入在jvm的内核当中,用c/c++编写,在java中找不到对应的实例。负责加载jdk中核心的类库比如:rt.jar,resources.jar
可以通过从属性sun.boot.class.path中看到BootStrap ClassLoader主要是从哪些地方加载相关的jar或class文件
System.out.println(System.getProperty("sun.boot.class.path"));
如下所示
D:\MyEclipse 2016 CI\binary\com.sun.java.jdk8.win32.x86_64_1.8.0.u66\jre\lib\resources.jar;
D:\MyEclipse 2016 CI\binary\com.sun.java.jdk8.win32.x86_64_1.8.0.u66\jre\lib\rt.jar;
D:\MyEclipse 2016 CI\binary\com.sun.java.jdk8.win32.x86_64_1.8.0.u66\jre\lib\sunrsasign.jar;
D:\MyEclipse 2016 CI\binary\com.sun.java.jdk8.win32.x86_64_1.8.0.u66\jre\lib\jsse.jar;
D:\MyEclipse 2016 CI\binary\com.sun.java.jdk8.win32.x86_64_1.8.0.u66\jre\lib\jce.jar;
D:\MyEclipse 2016 CI\binary\com.sun.java.jdk8.win32.x86_64_1.8.0.u66\jre\lib\charsets.jar;
D:\MyEclipse 2016 CI\binary\com.sun.java.jdk8.win32.x86_64_1.8.0.u66\jre\lib\jfr.jar;
D:\MyEclipse 2016 CI\binary\com.sun.java.jdk8.win32.x86_64_1.8.0.u66\jre\classes
ExtClassLoader:Extension ClassLoader 扩展的类加载器,加载目录为:%JRE_HOME%\lib\ext目录下的class文件。
AppClassLoader:加载当前应用中的classpath下的所有类
我们可以测试下:
public static void main(String[] args) {
System.out.println(Test.class.getClassLoader().toString());
System.out.println(String.class.getClassLoader().toString());
}
打印结果为:
sun.misc.Launcher$AppClassLoader@4e0e2f2a
Exception in thread "main" java.lang.NullPointerException
at test.Test.main(Test.java:10)
说明我们自己编写都Test类,是由AppClassLoader加载器进行加载的
但是String类,报的是空指针异常,原因就是上面提高的,String,Integer等都是BootStrap ClassLoader加载的。
可以看到:
static class ExtClassLoader extends URLClassLoader
static class AppClassLoader extends URLClassLoader
public class URLClassLoader extends SecureClassLoader implements Closeable
public class SecureClassLoader extends ClassLoader
ExtClassLoader和AppClassLoader最终都继承自ClassLoader,ClassLoader这个类中定义类加载的一些基本方法,具体代码我们在介绍完它的实现原理之后说明
ClassLoader的类加载原理
jvm中的类加载原理主要是双亲委托机制
在当前需要加载某个类时,并不是当前类立马调用加载,而是向上委托,每个加载器都有自己的一个缓存,首先是App ClassLoader在自己的加载缓存中查找,没有找到就向Ext ClassLoader进行委托,Ext ClassLoader也在自己的加载缓存中查找,没有找到就向BootStrap ClassLoader进行委托,BootStrap ClassLoader也是在自己的加载缓存中查找,如果也没有找到,则回到这个加载类本身,它此时会自己去加载,如果还是没有加载到,则抛出ClassNotFoundException异常。如果找到了,就将其加载到内存中,并返回这个类在内存中的实例。整个过程是一个由下至上的委托过程,但是是一个由上至下的查找过程。
采用双亲委托模型的原因主要是杜绝重复加载,因为如果当前需要加载某个类时,自己就去加载,那么有可能父加载器已加载过,比如BootStrap ClassLoader在jvm启动的时候会加载String类,后面某个应用类需要使用String的时候,再次加载String类,则不合理。
那么jvm是怎么判断一个类有没有已经加载过的呢?这里除了判断class文件是一致的外,还需要是同一个类加载器加载的才行。比如App ClassLoader和某个自定义的类加载器都加载了同一个Test类,那这两个Test类不是同一个类型,jvm会认为它们是两个类。
执行代码:
public class Test {
public static void main(String[] args) {
ClassLoader cl = Test.class.getClassLoader();
System.out.println(cl);
System.out.println(cl.getParent());
System.out.println(cl.getParent().getParent());
System.out.println(cl.getParent().getParent().getParent());
}
}
打印出:
sun.misc.Launcher$AppClassLoader@14dad5dc
sun.misc.Launcher$ExtClassLoader@368102c8
null
Exception in thread "main" java.lang.NullPointerException
at test.Test.main(Test.java:14)
第一行表示Test类是有AppClassLoader加载的
第二行表示Test类的父加载器,也就是AppClassLoader是有ExtClassLoader加载的
第三行表示ExtClassLoader的父加载器是null。之前已经提到,ExtClassLoader的父加载器是BootStrap ClassLoader,是jvm内核中的实现,所以这里显示为null。
参考推荐