1、类加载的时机
类从被加载到虚拟机内存到卸载出内存,一共包括:加载、验证、准备、解析、初始化、使用和卸载七个阶段。
虚拟机必须进行初始化的四种情况:
1、遇到new、getstatic、putstatic、或invokestatic这4条字节码指令时,如果类没有初始化则需要先进行初始化。比如使用new关键字实例化对象的时候、读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)、以及调用一个类的静态方法的时候。
2、使用java.lang.reflec包的方法对类进行反射调用的时候,如果类还没有初始化,则需要先出发初始化。
3、当初始化一个类的时候,发现其父类还没有进行过初始化,则需要先出发其父类的初始化。
4、当虚拟机启动时,用户需要先指定一个执行的主类,比如main()方法,虚拟机则会先初始化这个主类。
2、类加载的过程
2.1、加载
在加载阶段,虚拟机需要完成以下三件事情:
1、通过一个类的全限定名来获取定义此类的二进制字节流。(此阶段可控性最强,开发人员可以定义自己的类加载器去控制字节流的获取方式)
2、将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
3、在Java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口。
2.2、验证
验证的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机的自身的安全。主要包括:文件格式验证、元数据验证、字节码验证和符号引用验证。
2.3、准备
准备目的是正式为类变量分配内存并设置类变量初始值。类变量是被static修饰的变量,比如
public static int value=123
变量value在准备阶段过后的初始值为0而不是123,赋值是在初始化阶段执行的。
2.4、解析
解析的目的是虚拟机将常量池内的符号引用替换为直接引用的过程。
2.5、初始化
在初始化阶段,才真正开始执行类中定义的Java程序代码。初始化阶段是执行类构造器()方法的过程。
3、类加载器
类加载器的作用:类加载阶段中“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何获取所需要的类,这个动作的代码模块称为“类加载器”。
值得注意一点,判断两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义。否则,即使两个类来源于同一个class文件,只要加载它的类加载器不同,那这两个类就必定不相等。这里的“相等”包括class对象的equals()方法,isAssignableFrom()、instanceof关键字、isInstance()方法的返回结果。
3.1、双亲委派模式
类加载器只有两种:一种是启动类加载器(Bootstrap ClassLoader),这个类加载器是用C++语言实现的,是虚拟机自身的一部分。另外一种就是其他的类加载器,这些类加载器都是由Java语言实现,独立于虚拟机外的,并且全部继承抽象类java.lang.ClassLoader。分的再详细一点包括:
1、启动类加载器:负责将<JAVA_HOME>\lib目录下的jar文件加载到虚拟机中,启动类加载器无法被Java程序直接引用。
2、扩展类加载器:它负责将<JAVA_HOME>\lib\ext目录中的类库加载到虚拟机中,开发者可以直接使用。
3、应用程序类加载器:负责加载用户路径上所指定的类库,这也程序中的默认加载器,开发者可以直接使用。
如图所示的这种层次关系,就称为双亲委派模型。双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应当由自己的父类加载器。类加载器之间的父子关系一般不用继承,而用组合。
双亲委派模型的工作过程:如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个类委派个父类去完成。最终都会到顶层的启动类加载器中去,只有当父类反馈无法完成这个加载请求,子类加载器才会自己去加载。
双亲委派模型的好处:比如java.lang.Object (在rt.jar中)这个类,无论哪个加载器加载最终都会委派给启动类加载器进行加载,因此Object类在程序中各个类加载器环境都是用一个类。相反,如果没有双亲委派模型,各个类都自行加载,系统中将出现多个不同的Object类,从而造成混乱。
3.2、破坏双亲委派模型
第一次破坏双亲委派模型:JDK1.2之前还没有双亲委派模型,然后已经存在用户自定义类加载的实现代码。
第二次破坏双亲委派模型:双亲委派模式可以保证基础类被正确的加载,当基础类要调回用户代码(如JNDI、JDBC、JCE、JAXB),但启动类加载器并不认识这些代码,如何被加载?
第三次破坏双亲委派模型:在一些热部署的情况下,双亲委派模型将被破坏。
思考题
1、类在除了四种场景外都不能被初始化,那么类在哪些场景下将不会被初始化?
2、几种验证方式的具体操作?
3、搞清楚解析和初始化阶段的具体操作。
4、研究java.lang.ClassLoader中的loadClass方法,并尝试写个自定义的类加载器。