理解类加载机制
类加载器
1. 类加载器作用
类加载器本身作用就是用于加载类的,将类转换成java.lang.Class的实例;类加载器是通过类的全限定名来获取类的二进制字节流进而实现加载类。当我们比较两个类是否相等的时候,首先必须要确认是否由同一个类加载器加载,否则就算是使用相同class文件,被不同加载器加载比较结果还是不相等的。(相等可以指equals方法、isAssignableFrom方法、isInstance方法、instanceof方法等)。
2. 系统提供类加载器
① 启动类加载器Bootstrap ClassLoader
启动类加载器该加载器由jvm控制,没有父类加载器,也没有子类加载器。
专门加载<JAVA_HOME>\lib下或者-Xbootclasspath参数指定的路径下的类库,加载到jvm的内存中去。该类加载器使用c++语言实现,是虚拟机自身实现的一部分。
public static void main(String[] args) {
System.out.println(ClassLoader.getSystemClassLoader());
System.out.println(ClassLoader.getSystemClassLoader().getParent());
System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent());
}
## 结果
sun.misc.Launcher$AppClassLoader@3dccbdf7
sun.misc.Launcher$ExtClassLoader@4ccbc2d3
null
② 扩展类加载器 ExtClassLoader
扩展类加载器由sun.misc.Launcher$ExtClassLoader类加载器实现。
专门加载<JAVA_HOME>\lib\ext或者java.ext.dirs系统变量指定路径的类库;
public static void main(String[] args) {
String str = System.getProperty("java.ext.dirs");
System.out.println(str);
}
## 结果
D:\Java\java_123\jdk\jre\lib\ext;C:\Windows\Sun\Java\lib\ext
③ 应用程序类加载器 AppClassLoader
类加载器由sun.misc.Launcher$AppClassLoader实现。
专门加载类路径Classpath上指定的类库。(就是项目bin目录下的class文件)。
public static void main(String[] args) {
String str = System.getProperty("java.class.path");
System.out.println(str);
}
## 结果
D:\eclipseEE\workspace\spring_source\build\classes;
D:\Tomcat\Tomcat7\lib\annotations-api.jar;
D:\Tomcat\Tomcat7\lib\catalina-ant.jar;
D:\Tomcat\Tomcat7\lib\catalina-ha.jar;
。。。。
双亲委派模型
双亲委派模型除了顶层的启动类加载器之外,剩下的类加载器都必须要有自己的父类加载器(此处的父子关系不是继承而是组合)。

证明双亲委派模型向上委派例子:
public class MyLoader {
public void printLoader() {
System.out.println("My App ClassLoader");
}
}
public class CaseTest {
public static void main(String[] args) {
MyLoader myLoader = new MyLoader();
myLoader.printLoader();
}
}
运行结果:

追加到classpath的后面:-Xbootclasspath/a:E:\MyTemp\MyClass.jar;

运行结果:

反编译看下E:\MyTemp\MyClass.jar;

说明:从上面例子代码就可以看出,虽然应用程序类加载器加载了MyLoader.class文件,但是根据双亲委派模型,一个类加载器收到类加载的请求,不会自己去尝试加载,而是将这个请求委派给父类加载器,因为双亲委派模型是单向的,除非父类加载器加载不了,子加载器才会尝试自己去加载,而我们将MyLoader.class文件在启动类加载器中加载了一份,由于启动类加载器(父类加载器可以加载),那么就无需应用程序类加载器去实现了,所以我们看到的结果是 “ My Bootstrap ClassLoader ” 。
双亲委派模型总结:
类加载器接收到类加载请求不会马上去加载,而是委派父类去加载。
只有父类加载器无法加载(主要是在它的搜索范围内无法找到该类),子类加载器才会尝试去加载。
类加载流程

- 加载
① 虚拟机启动加载:
## -XX:+TraceClassLoading
[Opened E:\Java1.8\jdk\lib\rt.jar]
[Loaded java.lang.Object from E:\Java1.8\jdk\lib\rt.jar]
[Loaded java.io.Serializable from E:\Java1.8\jdk\lib\rt.jar]
[Loaded java.lang.Comparable from E:\Java1.8\jdk\lib\rt.jar]
[Loaded java.lang.CharSequence from E:\Java1.8\jdk\lib\rt.jar]
[Loaded java.lang.String from E:\Java1.8\jdk\lib\rt.jar]
[Loaded java.lang.reflect.AnnotatedElement from E:\Java1.8\jdk\lib\rt.jar]
[Loaded java.lang.reflect.GenericDeclaration from E:\Java1.8\jdk\lib\rt.jar]
。。。
② 运行时期加载:
通过一个类的全限定名来获取该class文件的二进制字节流。
将字节流的静态存储结构转化为方法区的运行时数据结构。
为上面的class文件,在堆中生成一个java.lang.Class对象对该类的数据访问入口。
- 连接
① 验证
这个阶段主要是验证被加载的类是否正确。目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
流程:文件格式验证、元数据验证、字节码验证、符号引用验证
② 准备
该阶段就是正式为类变量分配内存并设置类变量的初始值,注意类变量使用的内存在方法区上分配。(类变量是static修饰的变量,不包括实例变量,实例变量将会在对象实例化的时候随着对象一起分配在Java堆上)。
③ 解析
该阶段就是将jvm中常量池内的符合引用替换为直接引用的过程。符号引用、直接引用 - 初始化
初始化阶段做的事就是执行类的初始化方法<clinit>。方法<clinit>是由编译器自动生成的。就是将static变量赋值指定的值并且static静态代码块中赋值指定值。
注意:
① 父类定义的静态代码块优先于子类的静态代码块先执行。
② 子类引用父类的静态字段不会被初始化。
③ 前面我们提到过final修饰的static变量在准备阶段就在方法区上赋值了,在解析阶段中实际上<clinit>方法什么都不用做。final static 变量在编译期就完成了在元空间的赋值。而static 变量在准备阶段为默认值,初始化阶段进行赋值。