即将类从磁盘加载到Java虚拟机内存区中。
流程
验证-> 准备 -> 解析 -> 初始化
验证
验证就是验证字节码文件的格式是否正确。
准备
为静态变量分配内存并且赋默认值,例如int类型赋值0.
解析
将 符号引用替换为直接引用,将一些静态方法如main()方法替换为所在的内存的指针(直接引用),这就是静态链接的过程。该过程在程序运行期间完成的。
初始化
将类的静态变量初始化为指定的值,执行静态代码快。例如将某个int类型的值赋值为666。注意和准备阶段的区分,准备阶段是赋予默认值,例如0, null 等。
注意
- 只有当涉及到类的初始化的时候,才会去加载类,仅定义一个引用不会加载,即:
以上代码,不会加载B这个类,那么也不会去执行B中的静态代码块。B b = null;
类加载器和双亲委派机制
类加载器
类加载器的本质仍然是类, 类加载过程主要通过类加载器来实现。类型:
- 引导类加载器:加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如rt.jar, charsets.jar.
- 扩展类加载器:加载支撑JVM运行的位于JRE的lib目录下的ext目录下的JAR类包;
- 应用程序类加载器:说白了就是加载我们自己写的类,位于ClassPath路径下的类包;
- 自定义加载器:
双亲委派机制
即先向上委托上层加载器去加载,加载不到的时候才让下层加载器去加载。
PS: 并不是说扩展类加载器是应用程序类加载器的父类,只是说这两个加载器在结构上存在着亲子关系。
那么为什么需要双亲委派机制?
- 沙箱安全机制:例如自己写了一个String类,包名类名都和JDK自带的String类一致,那么自己的String类不会被加载,因为会一直委托父加载器去加载,父加载器可以成功找到String类,那么自己的这个String类就不会被加载了。
- 避免类的重复加载:如果父类加载器已经加载过某个类了,子加载器
最核心代码
findClass方法:根据类名,转化成对应的路径名,从磁盘中读取对应路径的类文件,加载进来。
如何打破双亲委派机制?
打破双亲委派机制指的是,当使用自定义加载器去加载类时,不再向上委托。
实现方式:
自定义加载器中重写LoadClass方法,删除掉双亲委派那部分代码。但仅仅这样说不够的,因为自己写的类要继承object类,该类是JAVA中的核心类,需要通过引导类加载器加载,但是目前使用的是自定义加载器,那么在指定的目录下没有object类,所以加载不到。
错误解决方式:
将object拷贝一份到指定的目录下,尝试通过自定义加载器加载object类。
错误原因:
JAVA给核心类加了沙盒保护机制,不允许用户通过自定义加载器加载这些核心类,以防止被重写导致双亲委派机制失去意义。
正确解决方式:
重写loadClass方法时,加一次判断,当加载我们自己的类时,打破双亲委派机制,否则调用parent的loadClass方法:
c = this.getParent().loadClass(name);