jvm - 类的编译,提到了把本地机器码转变成字节码,以及编译的优化。那这些字节码文件是怎么到JVM的呢?
一个类从被加载到虚拟机内存中开始,到卸载出内存,它的整个生命周期包括:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)7个阶段。
在Java虚拟机中类加载的全过程,包括加载、验证、准备、解析和初始化这5个阶段所执行的具体动做,这些都是有类加载器来实现的。java
加载是类加载过程的一个阶段。首先来一个简单的代码,打印###以及建立一个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文件的字节流中包含的信息符合当前虚拟机的要求,而且不会危害虚拟机自身的安全。当加载的class文件不符合虚拟机的要求,java虚拟机是没法执行这个字节码的,因此要先看看有没有符合,符合了才给虚拟机执行后续操做。jvm
准备是正式为类变量分配内存并设置类变量初始值的阶段。也就是说com.jvm.load.ClassLoad
和com.jvm.cls.Hello
在虚拟机中的内存分配是在这个阶段。这时候进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一块儿分配在Java堆中。设置类变量初始值一般状况下就是数据类型的零值。优化
// 准备阶段value=0 public static int value = 123; // 准备阶段value2=123 public static final int value2 = 123;
解析是虚拟机将常量池内的符号引用替换为直接引用的过程。
好比com.jvm.load.ClassLoad
编译的时候,不知道com.jvm.cls.Hello
的实际内存地址,此时用符号引用,当com.jvm.cls.Hello
加载到内存后,此时就改成直接引用,指向Hello的内存位置。spa
在准备阶段value=0,在初始化阶段,value才赋值为123。
类初始化的条件:代理
下面看看类虽然被加载,却没有初始化的例子。
SuperClass:code
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:blog
public class ClassLoad { public static void main(String[] args) { System.out.println("########################"); //Hello hello = new Hello(); System.out.println(SubClass.value); } }
运行结果以下:
能够看到SubClass被加载了,可是并无输出SubClass init
。
类加载器有这几个:
下图展现了类加载器直接的层次关系,成为类加载器的双亲委派模型。双亲委派模型要求除了顶层的启动类加载器外,其他的类加载器都应当有本身的父类加载器。
他的工做过程是这样的:
双亲委派模型一个显而易见的好处就是Java类随着它的类加载器一块儿具有了一种带有优先级的层次关系。