jvm - 类的编译,提到了把本地机器码转变成字节码,以及编译的优化。那这些字节码文件是怎么到JVM的呢?

一个类从被加载到虚拟机内存中开始,到卸载出内存,它的整个生命周期包含:加载(Loading)、验证(Verification)、筹备(Preparation)、解析(Resolution)、初始化(Initialization)、应用(Using)和卸载(Unloading)7个阶段。

在Java虚拟机中类加载的全过程,包含加载、验证、筹备、解析和初始化这5个阶段所执行的具体动作,这些都是有类加载器来实现的。

类加载

加载

加载是类加载过程的一个阶段。首先来一个简略的代码,打印###以及创立一个Hello对象。

public class ClassLoad {    public static void main(String[] args) {        System.out.println("########################");        Hello hello = new Hello();    }}

运行之前,设置-XX:+TraceClassLoading

运行后果如下(截取前面局部),能够看到com.jvm.load.ClassLoad先被加载,而后是com.jvm.cls.Hello。ClassLoad是这个main办法的主类,所以优先加载。Hello的加载,是在实例化的时候,也就是被用到的时候,如果读者本人去断点,那就更直观的看到了。

下面这个图,能够看到输入了类的全限定名,类加载器就是通过这个来获取它的二进制字节流,这个二进制字节流起源如下:

  • class文件
  • zip、jar、war包中读取
  • 网络中读取,比方Applet
  • 运行时计算生成,比方动静代理技术
  • 由其余文件生成,比方JSP

验证

验证是为了确保Class文件的字节流中蕴含的信息合乎以后虚拟机的要求,并且不会危害虚拟机本身的平安。当加载的class文件不合乎虚拟机的要求,java虚拟机是无奈执行这个字节码的,所以要先看看有没有合乎,合乎了才给虚拟机执行后续操作。

筹备

筹备是正式为类变量分配内存并设置类变量初始值的阶段。也就是说com.jvm.load.ClassLoadcom.jvm.cls.Hello在虚拟机中的内存调配是在这个阶段。这时候进行内存调配的仅包含类变量(被static润饰的变量),而不包含实例变量,实例变量将会在对象实例化时随着对象一起调配在Java堆中。设置类变量初始值通常状况下就是数据类型的零值。

// 筹备阶段value=0public static int value = 123;// 筹备阶段value2=123public static final int value2 = 123;

解析

解析是虚拟机将常量池内的符号援用替换为间接援用的过程。
比方com.jvm.load.ClassLoad编译的时候,不晓得com.jvm.cls.Hello的理论内存地址,此时用符号援用,当com.jvm.cls.Hello加载到内存后,此时就改为间接援用,指向Hello的内存地位。

初始化

在筹备阶段value=0,在初始化阶段,value才赋值为123。
类初始化的条件:

  1. new一个对象,动态变量的赋值和取值,静态方法的调用。
  2. 通过反射机制调用。
  3. 如果子类初始化的时候,父类未初始化。
  4. 执行的主类(main办法)的时候。

上面看看类尽管被加载,却没有初始化的例子。
SuperClass:

public class SuperClass {    static {        System.out.println("SuperClass init");    }    public static int value = 123;}

SubClass:

public class SubClass extends SuperClass {    static {        System.out.println("SubClass init");    }}

ClassLoad:

public class ClassLoad {    public static void main(String[] args) {        System.out.println("########################");        //Hello hello = new Hello(); System.out.println(SubClass.value);    }}

运行后果如下:

能够看到SubClass被加载了,然而并没有输入SubClass init

类加载器

类加载器有这几个:

  • 启动类加载器:jvm启动的时候,会优先加载<JAVA_HOME>\lib这个目录的外围类库。
  • 扩大类加载器:负责加载<JAVA_HOME>\lib\ext这个目录的类。
  • 应用程序类加载器:负责加载咱们写的代码。
  • 自定义类加载器:依据咱们的须要,加载特定的类。

下图展现了类加载器间接的档次关系,成为类加载器的双亲委派模型。双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都该当有本人的父类加载器。

他的工作过程是这样的:

  1. 应用程序类加载器收到了Hello类的加载申请,先问扩大类加载器是否能够加载。
  2. 扩大类加载器也不会间接去加载,他也是向下级启动类加载器询问是否能够加载。
  3. 启动类加载器在本人负责的目录搜寻了一下,发现自己找不到这个类,就说不行,你本人加载吧。
  4. 扩大类加载器在本人负责的目录搜寻了一下,发现自己找不到这个类,就说不行,你本人加载吧。
  5. 应用程序类加载器在本人负责的目录搜寻了一下,找到了这个类,把Hello类加载进来。


双亲委派模型一个不言而喻的益处就是Java类随着它的类加载器一起具备了一种带有优先级的档次关系。