类的生命周期:加载、验证、准备、解析、初始化、使用和卸载这7个阶段。其中,验证、准备和解析可统称为链接。java
加载与链接阶段的部份内容(如一部分字节码文件格式验证动做)是交叉进行的,加载阶段还没有完成,链接阶段可能已经开始。可是这两个阶段总的开始时间和完成时间老是固定的,加载老是在链接以前开始,链接老是在加载完成以后完成。-Xverify:none关闭验证,只有加载阶段用户可控,其它都由JVM完成。bootstrap
四个验证阶段:文件格式、元数据、字节码、符号引用。数组
类加载过程的第一个阶段:加载,此时虚拟机须要完成三件事情:缓存
一、 经过类的全限定名来获取类的二进制字节流。安全
执行文件格式验证,验证字节流能正确地解析,验证经过后,字节流存贮在方法区,后面的三个验证都是基于方法区的存储结构进行。数据结构
二、 将字节流的静态存储结构转化方法区的运行时数据结构。jvm
三、 在堆中生成一个表明该类的java.lang.Class对象,做为方法区这些数据的访问入口。ide
准备阶段:为类的静态变量分配内存并将其初始化为默认值,若是字节码含有ConstantValue属性的字段(final 属性),准备阶段会将其初始化为指定值,这些内存都将在方法区中进行分配。准备阶段不分配类中的实例变量的内存,实例变量将会在对象实例化时随着对象一块儿分配在堆中。函数
类加载器:
类加载由JVM外部实现,让应用程序决定如何获取所需的类,JVM提供了3种类加载器:
一、Bootstrap:根加载器,本地代码(C++)实现,加载基础核心类库(rt.jar);
二、Extension:从java.ext.dirs系统属性所指定的目录中加载类库,它的父加载器是Bootstrap;
三、System:应用类加载器,父类是Extension,是应用最普遍的类加载器。从classpath或者系统属性java.class.path所指定的目录中加载类,是用户自定义加载器的默认父加载器。优化
若使用自定义的类加载器(java.lang.ClassLoader的子类),则在字节码的方法表存储classLoader的引用,jvm在动态连接的时候,用该加载器加载引用类。为了正确动态连接和维护多个命名空间,jvm须要知道方法表里存贮的类加载器。
java.lang.ClassLoader内部维护着一个线程安全的HashTable<String,Class>,用于实现对Class字节流解码后的缓存,若是HashTable中已经有了缓存,则直接返回缓存。
当class已经被Application类加载器加载过了,而后若是想要使用Extension类加载器加载这个类,将会抛出java.lang.ClassNotFoundException异常。
注意:父加载器不能查找子加载器里的类。
类加载器能够装载一个类,却不能够卸载它,能够删除当前的类加载器,而后建立一个新的类加载器。
当一个类加载器被请求加载类时,在缓存里查看这个类是否已经被本身装载过了,若是没有的话,继续查找父类的缓存,直到在bootstrap类装载器里也没有找到的话,它就会本身在文件系统里去查找而且加载这个类。
类的预加载与首次主动使用:
类加载器并不须要等到某个类被“首次主动使用”时再加载它。JVM规范容许类加载器在预料某个类将要被使用时就预先加载它,若是在预先加载的过程当中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误) 若是这个类一直没有被程序主动使用,那么类加载器就不会报告错误 。
类的加载会致使父类的类和接口也会被加载进来。
链接阶段的符号引用的解析:
符号引用:
符号引用是一个字符串,它给出了被引用的内容的名字而且可能会包含一些其余关于这个被引用项的信息——这些信息必须足以惟一的识别一个类、字段、方法。这样,对于其余类的符号引用必须给出类的全名。对于其余类的字段,必须给出类名、字段名以及字段描述符。对于其余类的方法的引用必须给出类名、方法名以及方法的描述符。
直接引用:
直接引用解析后,放到运行时常量池里。
一、对于类的Class对象、类变量、类方法的直接引用多是指向方法区的本地指针。
二、对于实例变量、实例方法的直接引用都是偏移量。
实例变量的直接引用多是从对象的映像开始算起到这个实例变量位置的偏移量。
实例方法的直接引用多是方法表的偏移量。
子类中方法表的偏移量和父类中的方法表的偏移量是一致的,好比说父类中有一个say()方法的偏移量是7,那么子类中say方法的偏移量也是7。
经过“接口引用”来调用一个方法,jvm必须搜索对象的类的方法表才能找到一个合适的方法。这是由于实现同一个接口的这些类中,不必定全部的接口中的方法在类方法区中的偏移量都是同样的。他们有可能会不同。这样的话可能就要搜索方法表才能确认要调用的方法在哪里。
而经过“类引用”来调用一个方法的时候,直接经过偏移量就能够找到要调用的方法的位置了。【由于子类中的方法的偏移量跟父类中的偏移量是一致的】
因此,经过接口引用调用方法会比类引用慢一些。
初始化:
若是碰到在本类中声明本类的静态对象,且实例化,<cinit>()嵌套<init>()方法,则实例初始化可能在类初始化以前。
到了初始化阶段,才真正开始执行类中定义的Java程序代码。
一、static final int VAL = 100,编译时肯定的常量:基本数据类型的常量、String,不包括任何new对象和须要在运行时才能肯定的值。编译阶段会为VAL生成ConstantValue属性,在准备阶段虚拟机会根据ConstantValue属性将VAL赋值为100。
基本数据类型(不含包装类)的常量拷贝,不进入常量池,编译器把他们看成值(value)而不是域(field)来对待。直接把这个值插入到字节码中。这是一种颇有用的优化,若是是byte、short、int 数据,还会根据实际精度选择不一样类型的字节码命令,如bipush、sipush、iconst,和定义的类型不要紧。long类型是ldc命令。String的ldc #常量池编号。
二、类初始化:执行<clinit>()方法,是由javac自动收集类中的全部类变量的赋值动做和静态语句块static{}中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的,如:
static int i=1; static{i=0;} //i最终是0
三、接口的<clinit>()方法不须要先执行父接口的<clinit>()方法。只有当父接口中定义的变量使用时,父接口才会初始化。
接口的实现类在初始化时也同样不会执行接口的<clinit>()方法。
四、虚拟机规范严格规定了有且只有5中状况(jdk1.7)若是类没有进行过初始化,必须对类进行“初始化”:
1. new:建立对象(经过数组定义来引用类,不触发初始化)。
getstatic、putstatic读取/设置静态非final变量,如:static int a = 1,准备阶段赋初始值0,初始化阶段赋定义值1,谁定义初始化谁,和调用者无关。
invokestatic:执行静态方法。
2.使用java.lang.reflect包的方法对类进行反射调用。
3.子类初始化,触发父类的初始化,虚拟机会保证父类的<clinit>优先执行,则父类中定义的静态语句块要优先于子类的变量赋值操做。
4.当虚拟机启动时,用户须要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。
5.当使用jdk1.7动态语言支持时,若是一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getstatic,REF_putstatic,REF_invokeStatic的方法句柄,而且这个方法句柄所对应的类没有进行初始化,则须要先出触发其初始化。
五、如下状况不会触发初始化:
定义对象数组,不会触发该类的初始化
经过类名获取Class对象,不会触发类的初始化。
经过Class.forName加载指定类时,若是指定参数initialize为false时,也不会触发类初始化
经过ClassLoader默认的loadClass方法,也不会触发初始化动做,new ClassLoader(){}.loadClass("xxx.Cat");
对象建立过程:
1在堆内存中开辟一块空间,并给空间分配一个地址
2把对象的全部非静态成员加载到所开辟的空间下,并进行默认初始化,而后调用构造函数。
在构造函数入栈执行时,分为两部分:先执行构造函数中的隐式三步,再执行构造函数中书写的代码
6.一、隐式三步:
1,执行super语句
2,对开辟空间下的全部非静态成员变量进行显式初始化
3,执行构造代码块
6.二、在隐式三步执行完以后,执行构造函数中书写的代码
7在整个构造函数执行完并弹栈后,把空间分配的地址赋值给一个引用对象