一、类的加载过程
1、加载:
1.1、通过类的全限定名来获取类的二进制字节流。
1.2、将字节流转换为方法区的运行时数据结构。
1.3、在内存中生成代表该类的java.lang.Class对象,作为该类的访问入口。
2、验证
2.1、验证字节流是否符合Class文件规范。
2.2、对字节码描述的语义进行分析。
2.3、验证语义的合法性。
3、准备
3.1、在方法区中,为类变量(static修饰的变量)分配内存,并赋初始化值。(初始化值:根据数据的默认类型设置零值)
初始化值举例:
public static int value = 123;
那么准备阶段执行完后,value = 0 。在初始化阶段,才变为 value = 123 。
4、解析
4.1、将常量池的符号引用替换为直接引用。
符号引用:符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要能唯一标识目标即可。
直接引用:直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。
举例:
public class A{
private List<Object> lists;
}
用类A的加载器,根据List的全限定名,加载List类,并将加载后的List类的访问入口,指向lists这个变量。(符号引用替换为直接引用)
5、初始化
5.1、初始化阶段,编译器将执行所有类变量、静态语句块的赋值动作。
执行初始化的顺序为:
根据语句在源文件中的先后顺序来顺序执行。先执行父类的初始化,再执行子类的初始化。
注意:
虚拟机会保证多线程环境下,初始化过程只能执行一次。
二、类加载器
类加载器:通过类的全限定名来获取类的二进制字节流。
类加载器分为两种:
启动类加载器:c++语言实现,虚拟机的一部分。
其他类加载器:继承于java.lang.ClassLoader。
启动类加载器:负责将<JAVA_HOME>lib目录下的,类库加载到虚拟机中。
扩展类加载器:加载<JAVA_HOME>libext 目录中的类,可以被开发者直接使用。
应用程序加载器:负责加载用户类库路径(ClassPath)中的类库,可以被开发者直接使用。
自定义类加载器:加载自定义的类库。
双亲委派模型工作过程:
如果一个类加载器收到了类的加载请求,它首先不会自己去尝试加载这个类,而是把这个加载请求委派给父类加载器去完成,每一个层次的类加载器都是如此。因此所有的加载请求最终都会传递到顶层的启动类加载器中,只有当父类加载器反馈自己无法完成加载请求(在自己的搜索范围内,找不到该类)时,子类才会去尝试自己去加载。
双亲委派模型的意义:
1、确保唯一性。确定一个class的唯一性是由类加载器和对应的class来唯一确定的。如果不适应双亲模型,那么根类加载器和系统类加载器都加载了一个名为String的类,那么在使用的时候,值一样的对象equals就不相等。
2、安全性。防止通过网络的形式,篡改系统库,即rt.jar。
双亲委托模型的缺点:
1、父加载器所加载的类,无法使用子加载器所加载的类。(解决的方法:线程上下文类加载类)
每一个类都会使用自身的类加载器来加载所依赖的类。
线程上下文类加载器:
在面向接口的编程环境下,接口类是类库定义的,实现类是任意的。那么启动类加载器可以加载到接口类,但是如何识别实现类呢?此时就需要到线程上下文类加载器。
线程上下文类加载器(Thread Context ClassLoader)。Thread类中有getContextClassLoader()和setContextClassLoader(ClassLoader cl)方法用来获取和设置上下文类加载器,如果没有setContextClassLoader(ClassLoader cl)方法通过设置类加载器,那么线程将继承父线程的上下文类加载器,如果在应用程序的全局范围内都没有设置的话,那么这个上下文类加载器默认就是应用程序类加载器(Application ClassLoader),换句话说Java默认的线程上下文类加载器就是应用程序类加载器(AppClassLoader)。
一般使用模式:
类的卸载:仅自定义加载器所加载的类,才能进行卸载。