类在虚拟机中的生命周期:加载(Loading)、验证(Verification)、准备(preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)、卸载(Unloading) 共7个阶段java
加载、验证、准备、初始化、卸载 这5个阶段有严格的前后顺序关系。其余阶段不必定。(为了支持java语言的运行时绑定)spring
加载由3部分组成:json
第1点是很是灵活的,好比能够从jar包获取,能够从网络获取,能够动态生成(动态代理、数组类),能够由其余文件生成(好比jsp)等等。设计模式
class文件运行与JVM中。.class能够是.java文件经过javac编译而成,也能够是groovy文件经过groovyc编译而成,也能够是JRuby文件经过JRbubyc编译而成,也能够是其余语言程序(好比scala)经过对应的编译器编译而成。因此JVM对class文件的验证就显得很是重要,以防止对正在运行的JVM形成伤害。数组
验证包括一下几部分网络
准备:为类变量分配内存并设置默认初始值(0,false,null等)。若是为final,则分配内存并设置值。数据结构
解析:虚拟机将常量池内的编译器使用的符号引用替换为运行期使用的直接引用的过程(配合验证阶段--的第4步)。多线程
符号引用:符号引用以一组符号来描述所引用的目标,符号能够是任何形式的字面量,只要使用时可以无歧义的定位到目标便可。符号引用是由于在编译的时候,不能明确所引用的类或属性或方法在运行时的具体地址,因此就以符号引用来代替。jvm
直接引用:在运行期能直接肯定目标的指针、相对偏移量或间接定位的句柄。HotSpot采用指针。jsp
JVM规范并未规定解析阶段发生的具体时间。只要求在16个特定的字节码指定执行以前,先对他们所使用的符号引用进行解析。因此虚拟机实现能够根据须要判断究竟是在类呗加载器加载时就对常量池中的符号引用进行解析,仍是等到一个符号引用将要被使用以前才去解析它。通常来讲如今的虚拟机都选择后者,这样能够边使用边解析,提升访问速度(暂时不解析还没用使用到的)。
初始化:执行<clint>()方法 字节码中的<clint>()方法由编译器对类变量的赋值动做和静态代码块按代码编写顺序合并而来。若是一个类没有类变量也没有静态代码块,则该类编译成的字节码中没有<clint>()方法。 由虚拟机来保障在多线程的状况下,只有一个线程去执行这个类的<clint>()方法,其余线程阻塞等待直到<clint>()完成。
类加载器:
类加载器比较灵活,开发者能够继承java.lang.ClassLoader来实现本身的类加载器(好比spring、fastjson等都有类加载器的实现)。
双亲委派: 当碰到一个须要被加载的Class的时候,该加载器会委托父加载器加载。当父加载器范围未加载而且不能加载该类的时候,才有该加载器加载类。这样作的目的是为了保障加载到的class文件存放到内存中的java.lang.Class对象是同一个。父加载器与子加载器不是经过继承实现的,而是组合。 注意:不一样ClassLoader加载同一个class文件,会生成不一样的java.lang.Class对象,致使java代码中 instanceof 返回false
class文件以何种格式存储,类型什么时候加载、如何链接,以及虚拟机如何执行字节码指令都是有虚拟机直接控制的行为,用户编写的程序代码没法直接控制和改变。用户能经过程序进行操做的,主要是字节码的生成与类加载器这两部分。
须要对类进行初始化的场景:
注意,
获取class对象的三种方式:
这三种方式中,第一种若是是首次加载只会触发TestA对应的class的加载、验证、准备,不必定会有解析,但必定不会初始化(即,static代码块不会执行)。第二种方式若是是首次加载会在第一种的基础上多执行一次初始化。第三种方式该类的对象都已经存在了,说明该类已经进行过来初始化。
参考资料: