任何一个字节码流能够惟必定义一个类或接口,下文将字节码流统称为字节码文件java
类生命周期:加载,链接(验证,准备,解析),初始化,使用,卸载 , 其中加载,验证,准备,初始化,使用,卸载等步骤相对顺序稳定,而解析阶段能够在初始化前进行操做,也能够在初始化操做后真正使用符号引用再进行解析操做,也正是由于能够在初始化后真正使用时进行解析操做,才能够支持java的动态解析,如:直到调用对象的方法时,再去解析方法引用,这种状况只有在运行时才能直到真正的调用对象数组
类加载过程:将指定环境下的字节码文件加载至虚拟机内存,通过加载,链接,解析操做造成正确有效可用的java对象,注意:此处java对象并不是实例对象,而是指Class对象,用于定义java类元数据信息缓存
加载阶段:包括三个步骤,1.将字节码文件经过类加载器加载至虚拟机内存 2.将字节码文件中静态结构转换为方法区的运行时数据结构 3.根据方法区运行时数据结构构造对象元对象Class 安全
将字节码文件加载至虚拟机内存,此项操做比较宽松,jvm并无规范从哪里加载字节码文件,因此咱们能够从本地文件系统加载字节码文件,也能够经过网络传输加载,还能够从zip压缩包中加载字节码文件,甚至能够从内存中计算生成字节码流记载至虚拟机内存。加载完字节码文件后,根据字节码文件静态数据结构将类信息,常量池等转换为方法区的运行时数据结构,最好根据方法区运行时数据结构生成类元对象Class,jvm并无规范Class对象必定要存放在堆区,各种虚拟机可自主实现Class对象的存储位置,HotSpot虚拟机将Class对象存放在方法区网络
链接阶段:验证,准备,解析数据结构
验证:验证操做并非等到加载操做彻底完成后才开始执行,通常在进行类加载操做的时候会同步执行验证操做,验证操做具体包括:文件格式验证,元数据验证,字节码验证,符号引用验证。文件格式验证操做主要验证字节码文件是否符合jvm规范,好比文件魔术是否为cafebabe,文件的主次版本是否能被本虚拟机接受执行等操做,确保字节码执行不会危害虚拟机安全,虽然编译过程会对一些有编译错误的代码拒绝编译,可是因为虚拟机并无规定字节码文件的来源,咱们能够用16进制编辑器编写恶意代码,若是没有文件格式验证,可能会危害虚拟机的自身安全。元数据验证操做主要包括这个类是否有父类,除java.lang.Object每一个类都要有父类,且只能有一个父类,一个类是否继承了不能被继承final的父类,一个实现类是否实现抽象父类或父接口的抽象方法,是否重写了父类的final方法等,确保类的元数据的正确性。字节码验证操做主要字节码的语义,好比类型是否强制转换为不能强转的类型,保证read,load操做相同类型,保证跳转指令不会跳转至方法体外的指令上去等操做。符号引用验证操做主要符号引用的合法性,结合解析阶段使用,确保符号引用能找到对应的类或接口,确保字段或方法的符号引用能找到对象的字段或方法,验证对符号引用表示的类型的访问权限等操做jvm
准备:在方法区为字节码流分配内存,在Class对象为类字段赋初值,好比private static final a=100,此处在准备阶段类变量被分配内存并赋初值a=0 注意:此处只对类变量赋初值,对实例变量阶段不作处理,对常量会根据字段的ContantValue值对常量赋值,final int a=100 此处在准备阶段a被赋值为100编辑器
解析:将常量池的符号引用转换为直接引用,符号引用与本地机器无关,并且符号引用表示的类并不必定须要加载至内存,能够直接使用符号引用表明某个类,并不要指向具体的目标代码,但在使用的使用须要将符号引用表示的类加载至内存,直接内存与本地内存相关,能够根据直接引用直接或间接定位到目标代码,好比 java.long.Object符号引用表示指向Object类型的符号,此时Object类并不必定须要加载至内存,而将java.lang.Object转换为直接引用时,则java.lang.Object指向方法区Object类的地址。解析操做涉及常量池符号引用的解析,包括constant_class_info,constant_fieldref_info,constant_methodref_info,constant_interfacemethodref_info,constant_nameandtype_info,constant_methodtype_info,constant_methodhandle_info常量项的解析,除了constant_methodhandle_info常量项的解析操做,其余类型的解析操做解析成功一次后会对解析结果进行缓存,提升下次解析的效率,而constant_methodtype_info自己就是动态类型,每次的解析结果都不同,则不会进行缓存,主要是类与接口的解析,字段解析,方法解析,接口方法解析,方法句柄解析。类与接口解析:若是对象类型为非数组引用类型,那么根据类或接口的全限定名经过类加载器加载至内存将符号引用转换为指向类内存结构的直接引用,若对象类型为数组类型,那么虚拟机直接构造数组类型内存结构,将符号引用指向内存数组对象的直接引用,而后判断当前类对引用类型的访问权限。字段解析:根据field_info中class_index属性找到字段所属类,若类不存在方法区,在进行类加载操做,若找不到类则直接失败,若类class存在,则在class类中查找与field简单名称和字段描述符都相同的属性(每一个类中存储的字段都是本类声明的字段,不包括从父类或父接口继承来的字段),若是找到,则直接返回,若没有,则在父接口中递归查找,找到则返回,若没有,则再在父类中递归查找,若存在,则直接返回,若没有则失败。方法解析:方法解析与字段解析不一致,方法解析优先查本类,再递归查父类,最好递归查父接口,而字段解析中优先查本类,但再递归查父接口,最好递归查父类,方法解析其余具体步骤与字段解析一致,只是在父类或父接口的前后递归查找顺序不一致。接口方法解析独立出来,与方法解析有点不同,由于接口方法只能出如今接口中,不会出如今类中,因此接口方法解析过程为根据class_index查找类或接口,若是找到class为类而不是接口时则直接失败,不然在本接口查找与name_and_type_index相同的方法简单名称和描述符,若找到则直接返回,没有则递归在父接口查找,若是找到则返回,没有找到则解析失败加密
初始化:初始化阶段是类加载的最后一个阶段,虚拟机根据代码中静态变量的赋值和静态代码块的操做按代码顺序自动生成<cinit>类初始化方法,根据开发人员的自主定义初始化类变量,类,抽象类,甚至接口在进行类加载时,虚拟机都会自动生成<cinit>,不过接口中不容许有静态代码块,也接口的类变量全是final常量,这些字段的赋值在准备阶段已经完成,因此接口中<cinit>方法没太大意义,因此<cinit>方法对类或接口并非必须的,若是类或接口既没有静态语句块,也没有类变量的赋值语句,则虚拟机不会去建立<cinit>方法,且为了保证加载类的惟一性,虚拟机自动为<cinit>添加同步语义,即一个线程在进行类初始化时,其余对须要同一个类进行初始化操做的线程须要阻塞等待。jvm定义了有且只有下面5种状况会触发类初始化spa
1.实例化某类对象,访问类的静态字段,访问类的静态方法,即遇到new,putstatic,getstatic,invokestatic指令时若是类没有进行初始化则会进行类初始化
2.经过反射实例化对象,访问类的字段,方法时若是类没有进行初始化则会进行初始化操做
3.进行子类初始化时,若是发现父类没有进行初始化操做,则会先触发父类的初始化操做(根据此条规则说明第一个初始化的类必定是Object,由于Object是全部引用类型的父类,包括数组类型)
4.虚拟机会自动执行运行主方法的类的初始化操做,即运行main方法的类运行以前会进行初始化操做
5.当使用jdk7动态语言支持时,若是constant_methodhandle_info常量项的解析结果为ref_putstatic,ref_getstatic,ref_invokestatic时,若是解析结果对应的类没有进行初始化时,则会触发对应类的初始化操做
虚拟机规范代表有且只有上述五种状况会进行类初始化操做,上述操做被称为主动引用,其余一些方法看似调用了类的字段或方法,但不会触发对应类的初始化,那些操做被称为被动引用。被动引用举例:1.在子类中引用父类的类变量,通常来讲子类会继承父类的类变量,但经过子类访问类变量时,只会触发父类初始化,而不会触发子类初始化 2.访问某个类的常量属性时,不会触发类初始化,常量赋值在准备阶段已经完成,引用常量字段可直接经过常量池访问,不会触发类初始化 3.初始化一个引用数组时,不会触发数组元素引用类型的初始化,而是进行数组元素组成的数组类型的初始化,如 Person[] o=new Person[10],不会触发person类初始化,而是触发[com.test.Person 类初始化
类加载器:类加载器主要进行类或接口加载过程,注意:数组类型直接由虚拟机进行加载,不是由类加载器加载,将特定环境下的字节码流加载至内存,类加载器主要考虑的问题:在哪里加载字节码流?如何加载字节码流,加载字节码流前的操做?将字节码流如何转换成Class对象?基于前面的问题,类加载器的主要方法有findClass(),defineClass(),finfClass方法用于在何处找到字节码流以及对字节码流的后续处理,defineClass方法用于将字节码流转换为Class对象。从何处加载字节码流:能够从本地文件系统直接加载.class字节码流文件,也能够从zip压缩文件加载字节码流,能够从网络传输中加载字节码流,能够内存中直接生成字节码流。对应字节码流的操做:加密解密操做。类加载器种类:启动类加载器BootstrapClassLoader,扩展类加载器ExtClassLoader,应用类加载器AppClassLoader,自定义类加载器,启动类加载器负责加载java_home/lib目录下的,或者加载虚拟机参数 -Xbootclasspath参数目录下的指定文件名表示的class,好比rt.jar,一些非法不能识别的文件名即便在加载目录下也不会被加载。ExtClassLoader扩展类加载器主要负责加载java_home/ext目录下的class文件,AppClassLoader应用类加载器主要负责加载classpath目录下的class文件,经过System.getProperty("java.class.path")可获取classpath。因为BootstrapClassLoader类加载器直接由C++代码实现,并非ClassLoader子类,咱们不能控制BootstrapClassLoader加载,由虚拟机自动控制,其次须要注意的是:AppClassLoader类加载器是ExtClassLoader类加载器的子类
双亲委派模型:当一个类加载器被系统要求去加载某个class文件时,这个类加载器首先判断本身是否有父类加载器,若是没有则由BootstrapClassLoader启动类加载器先进行类加载操做,若是有父类加载器,则先由父类加载器进行类加载操做,以上两种状况可将BootstrapClassLoader类加载器当作是全部类加载器的父类,即:能够说成先由父类加载器尝试加载类。若是父类加载器加载成功,则直接返回父类加载器加载的类,不然子类加载器自己进行加载类操做,若是加载成功,则返回自身加载的类,不然返回类加载失败。为何要根据双亲委派模型进行类加载操做?保证加载类的全局惟一性,即父类加载器负责目录下的class文件不会被子类相同类型的class文件所覆盖,好比你能够在classpath目录下建立一个java.lang.Object字节码文件,根据双亲委派模型,会先由启动类加载器加载java_home/lin目录下的java.lang.Object字节码文件,不会去加载自主定义的字节码文件