类从被加载到虚拟机内存开始,到卸载出内存为止,大致的生命周期为:加载(loading)->验证(Verification)->准备(Preparation)-》解析(Resolution)-》初始化(Initialization)->使用(Using)-》卸载(Unloading)等七个阶段。
在这当中,加载,验证,准备,初始化和卸载五大阶段的顺序是确定的,类的加载过程必须按照这种顺序按部就班的开始,而解析阶段则不一定,为了支持Java的运行时动态绑定,在某些情况下可以在初始化阶段之后再开始。
针对于初始化阶段,虚拟机规范严格规定了有且只有5中情况下必须立即对类进行“初始化”:
Java是使用双亲委派模型来进行类的加载,所以再描述类的加载过程前,我们先看一下它的工作流程:
双亲委派模型的工作流程:如果一个类加载器(ClassLoader)收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委托给父类加载器去完成,每一个层次的类加载请求最终都应该传送到顶层的启动类加载器中,只有当父类加载器反馈回来自己无法完成这个加载请求(即再它的搜索范围内没有找到所要加载的类)时,子类加载器才会尝试自己去加载。
使用双亲委派模型的好处在于,能够有效确保一个类的全局唯一性,当程序中出现多个限定名相同的类,类加载器在执行加载时,始终只会加载其中的一个类。
1.加载
“加载”是“类加载”的一个阶段,在该阶段,虚拟机需要完成3件事情:
2.验证
验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
2.元数据验证
对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范,其验证点有:
3.字节码验证
这个阶段是最复杂的阶段,主要目的是通过数据流和控制流分析,确定程序语义是合法的,符合逻辑的。继对元数据信息的数据类型做完校验,这个阶段对类的方法体进行校验分析,保证安全性:
3.准备
准备阶段是正式为类变量分配内存并设置变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。在这里,要明确的是这时候进行内存分配的仅仅包括类变量(被static修饰的变量),而不包括实例变量。实例变量将会在对象实例化时随着对象一起分配在Java堆中。
4.解析
将常量池中的符号引用转换为直接引用(得到类或者是字段,方法在内存中的指针或者是偏移量,以便直接调用该方法),这个可以在初始化之后再执行。
解析需要静态绑定的内容 //所有不会被重写的方法和域都会被静态绑定。
以上2,3,4三个阶段又合称为链接阶段,链接阶段要做的就是将加载到JVM中的二进制字节流的类数据信息合并到JVM的运行时状态中
5.初始化(先父后子)
(1)为静态变量赋值
(2)执行static代码块 (static代码块只能有JVM调用)
如果是多线程需要同时初始化一个类,仅仅只能允许其中一个线程对其执行初始化操作,其余线程必须等待,只有在活动线程执行完对类的初始化操作之后,才会通知正在等待的其他线程。因为子类存在着对父类的依赖关系,所以类的加载顺序是先加载父类后加载子类,初始化也一样。不过,父类初始化时,子类静态变量的值也是有的,为默认值。最终,方法区会存储当前类的类信息,包含类的静态变量,类初始化代码(定义静态变量时的复制语句和 静态初始化代码块),实例变量定义。实例初始化代码(定义实例变量时的赋值语句,实例代码块和构造方法)和实例方法,还有父类的类信息引用。
类初始化阶段是类加载过程的最后一步,这也是真正开始执行类中定义的Java字节码。初始化阶段是执行类构造器
Java在new一个对象的时候,首先会查看对象所属的类有没有被加载到内存,如果没有的话,将会通过类的全限定名来加载。加载并初始化类完成后,再进行对象的创建工作。
1.在堆区分配对象所需要的内存
分配的内存包括本类和父类的所有实例变量,但不包括任何静态便变量
2.对所有的实例变量赋予默认值
将方法区内对实例变量的定义拷贝一份到堆区,然后赋默认值
3.执行实例初始化代码
初始化顺序是 先初始化父类再子类。初始化时先执行实例代码块然后是构造方法
4,如果有类似于Children c=new Children()形式的c引用的话,在栈区定义Children类型引用变量C,然后将堆区对象的地址赋值给它。
需要注意的是,每个子类对象持有父类对象的引用,可在内部通过super()关键字来调用父类对象,但在外部不访问。
补充:
通过实例引用调用实例方法的时候,先从方法区中对象的实际类型信息中找,找不到的话再去父类类型信息中找。
如果继承的层次比较深,要调用的方法位于较上层的父类,则调用的效率是比较低下的,因为每次调用都要经过多次查找。这个时候大多系统会采用一种称为虚方法表的方法来优化调用效率。
所谓虚方法表,就是在类加载的时候,为每个类创建一个表,这个表包含了该类的对象所有动态绑定的方法以及其地址,包括父类的方法,但是一个方法只有一条记录,子类重写了父类方法后只会保留子类的。当通过对象动态绑定方法的时候,只需要查找这个表就可以了,而不需要挨个查找每个父类。
>参考《深入理解Java虚拟机》
>争渡争渡,惊起一滩欧鹭。
==欲知后事如何,请见下回分解==