我所理解的JVM(二):类加载机制

类在虚拟机中的生命周期:加载(Loading)、验证(Verification)、准备(preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)、卸载(Unloading) 共7个阶段java

加载、验证、准备、初始化、卸载 这5个阶段有严格的前后顺序关系。其余阶段不必定。(为了支持java语言的运行时绑定)spring

加载由3部分组成:json

  1. 这里是列表文本经过classloader获取该类的二进制字节流。
  2. 将字节流所表明的静态存储结构转化为运行时数据结构。
  3. 在内存中生成一个表明这个的java.lang.Class对象,做为方法去这个类的各类数据的访问入口。

第1点是很是灵活的,好比能够从jar包获取,能够从网络获取,能够动态生成(动态代理、数组类),能够由其余文件生成(好比jsp)等等。设计模式

class文件运行与JVM中。.class能够是.java文件经过javac编译而成,也能够是groovy文件经过groovyc编译而成,也能够是JRuby文件经过JRbubyc编译而成,也能够是其余语言程序(好比scala)经过对应的编译器编译而成。因此JVM对class文件的验证就显得很是重要,以防止对正在运行的JVM形成伤害。数组

验证包括一下几部分网络

  1. 文件格式验证:是否已魔法书0xCAFEBABE开头,主次版本号是不是当前JVM能够接受的,常量池中的常量是否有不被支持的常量类型,常量池中的各类索引值是否有指向不存在的常量或不符合类型的常量。。。。。等等
  2. 元数据验证:是否有父类,父类是否不容许被继承,是否实现了父接口的接口或者父抽象类的抽象方法,类的字段、方法是否与父类产生矛盾(父final变量不可覆盖,父final方法不可重写)。。。。等等
  3. 字节码验证。主要是对方法体内的代码进行验证,好比参数类型是否一致,类型转换是否有效。。。等等
  4. 符号引用验证。这一验证阶段配合第三阶段--解析阶段完成。主要验证引用类是否存在,类、字段、方法的访问性 等等。。。

准备:为类变量分配内存并设置默认初始值(0,false,null等)。若是为final,则分配内存并设置值。数据结构

解析:虚拟机将常量池内的编译器使用的符号引用替换为运行期使用的直接引用的过程(配合验证阶段--的第4步)。多线程

符号引用:符号引用以一组符号来描述所引用的目标,符号能够是任何形式的字面量,只要使用时可以无歧义的定位到目标便可。符号引用是由于在编译的时候,不能明确所引用的类或属性或方法在运行时的具体地址,因此就以符号引用来代替。jvm

直接引用:在运行期能直接肯定目标的指针、相对偏移量或间接定位的句柄。HotSpot采用指针。jsp

JVM规范并未规定解析阶段发生的具体时间。只要求在16个特定的字节码指定执行以前,先对他们所使用的符号引用进行解析。因此虚拟机实现能够根据须要判断究竟是在类呗加载器加载时就对常量池中的符号引用进行解析,仍是等到一个符号引用将要被使用以前才去解析它。通常来讲如今的虚拟机都选择后者,这样能够边使用边解析,提升访问速度(暂时不解析还没用使用到的)。

初始化:执行<clint>()方法 字节码中的<clint>()方法由编译器对类变量的赋值动做和静态代码块按代码编写顺序合并而来。若是一个类没有类变量也没有静态代码块,则该类编译成的字节码中没有<clint>()方法。 由虚拟机来保障在多线程的状况下,只有一个线程去执行这个类的<clint>()方法,其余线程阻塞等待直到<clint>()完成。

类加载器

  1. 启动类加载器:Bootstrap ClassLoader 负责加载<JAVA_HOME>/lib目录下的文件(如rt.jar)。
  2. 扩展类加载器:Extension ClassLoader 负责加载<JAVA_HOME>/lib/ext目录下的问题件。
  3. 应用程序类加载器:Application ClassLoader 负责加载ClassPath上的指定的类库。

类加载器比较灵活,开发者能够继承java.lang.ClassLoader来实现本身的类加载器(好比spring、fastjson等都有类加载器的实现)。

双亲委派: 当碰到一个须要被加载的Class的时候,该加载器会委托父加载器加载。当父加载器范围未加载而且不能加载该类的时候,才有该加载器加载类。这样作的目的是为了保障加载到的class文件存放到内存中的java.lang.Class对象是同一个。父加载器与子加载器不是经过继承实现的,而是组合。 注意:不一样ClassLoader加载同一个class文件,会生成不一样的java.lang.Class对象,致使java代码中 instanceof 返回false

class文件以何种格式存储,类型什么时候加载、如何链接,以及虚拟机如何执行字节码指令都是有虚拟机直接控制的行为,用户编写的程序代码没法直接控制和改变。用户能经过程序进行操做的,主要是字节码的生成与类加载器这两部分。

须要对类进行初始化的场景:

  1. new对象、设置或读取静态变量(同时被被final修饰除外,由于编译期已经把结果放入常量池)、调用类的静态方法
  2. 对类进行反射调用的时候
  3. 若是父类没有初始化,须要先初始化父类
  4. 程序入口main方法所在的类

注意

  1. 经过子类来调用父类的静态变量或方法,不会初始化子类;
  2. 定义数组的时候不会初始化类;
  3. 调用 fianl static 变量 不会初始化类。编译器已经将被调用的final值放在了调用者的常量池中。
  4. 只有在实际使用到父接口(好比使用父接口定义的常量)才会初始化父接口。(只经过java代码很差证实,能够配合使用jvmconsole等工具查看已加载类)
  5. 内部类会执行懒加载,第一次使用到的时候才执行初始化。(单例设计模式的一种)

获取class对象的三种方式:

  1. TestA.class;
  2. Class.forName("com.a.b.c.TestA.class");
  3. testA.getClass();

这三种方式中,第一种若是是首次加载只会触发TestA对应的class的加载、验证、准备,不必定会有解析,但必定不会初始化(即,static代码块不会执行)。第二种方式若是是首次加载会在第一种的基础上多执行一次初始化。第三种方式该类的对象都已经存在了,说明该类已经进行过来初始化。

参考资料:

  1. 《深刻理解Java虚拟机》
  2. JVM类加载的原理及实现
相关文章
相关标签/搜索