JVM虚拟机个人总结(三)

今天来讲一下关于类型的生命周期。

首先之前提到过类型的装载,连接与初始化。接下来详细的介绍一下各个过程。

Java虚拟机通过装载、连接和初始化一个Java类型,使该类型可以被正在运行的Java程序使用。其中,装载就是把二进制形式的Java类型读入Java虚拟机中;而连接就是把这种已经读入虚拟机的二进制形式的类型数据合并到虚拟机的运行时状态中去。连接状态分为三个自步骤——验证,准备和解析。“验证”步骤确保了Java类型数据格式正确并且适于Java虚拟机使用,“准备”步骤则负责为该类型分配他所需要的内存,比如为它的类变量分配内存。”解析“步骤则负责把常量池中的符号引用转化成直接引用、虚拟机的实现可以推迟解析这一步,它可以在当运行中的程勋真正使用某个符号引用时再去解析它(把该符号引用装换为直接引用)。
步骤如下图:
这里写图片描述

装载、连接和初始化必须按顺序执行,唯一的例外就是连接阶段的解析,它可以在初始化之后再进行。

类和接口在被装载和连接的时机上没有严格规定,但是所有虚拟机实现必须在每个类和接口首次主动使用时初始化,需要符合以下要求之一:
1.当创建某个类的新实例时(或者通过new,或者通过反射、克隆或反序列化)
2.当调用某个类的静态方法时
3.当使用某个类或接口的静态字段,或者对该字段赋值时,用final修饰的静态字段除外,它被初始化一个编译时的常量表达式
4.当调用Java API中的某些反射方法时,比如Class中的方法或者java.lang.reflect包中的类的方法。
5.当初始化某个类的子类时(某个类初始化时,要求它的超类已经被初始化了)
6.当虚拟机启动时某个被标明为启动类的类(即含有main()方法的那个类)

注意,对于接口来说,任何一个类的初始化要求它的超类在此之前已经初始化了这个不成立。只有某个接口所声明的非常量字段被使用时,接口才会被初始化。

下面是进行每个步骤的详细介绍:

装载:
装载阶段由三个基本动作组成:
1.通过该类型的完全限定名,产生一个代表该类型的二进制数据流
2.解析这个二进制数据流为方法区内的内部数据结构
3.创建一个表示该类型的java.lang.Class类的实例

产生二进制数据的方式:
1.从本地文件系统装载一个Java class文件
2.通过网络下载一个Java class文件
3.从一个zip、jar、cab或者其他某种归档文件中提取Java class文件
4.从一个专有数据库中提取Java class文件
5.把一个Java源文件动态编译为class文件
6.动态为某个类型计算其class文件格式
7.使用上述任何方法,但是使用不同于java class文件的其他二进制文件格式

有了类型的二进制数据后,Java虚拟机必须对这些数据进行足够的处理,才能创建java.lang.Class的实例对象,它成为Java程序与内部数据结构之间的接口。
虚拟机可以预先装载class文件,但是装载失败时不会马上报告错误,只有等到程序首次主动使用该类时才报告错误。

验证:
连接第一个步骤验证是为了确认类型符号Java语言的语义并且它不会危及虚拟机的完整性。
在正式验证阶段检查:
1.检查final的类不能拥有子类
2.检查final的方法不能被覆盖
3.确保在类型和超类型之间没有不兼容的方法声明(比如两个方法拥有同样的名字、参数在数量、顺序、类型上都相同,但是返回类型不同)
注意,当这些检查需要查看其他类型的时候,只需要查看超类型、超类型在子类初始化前全部初始化,当实现了父接口的类被初始化时,不需要初始化父接口,但是要装载父接口
4.检查所有的常量池入口相互一致
5.检查常量池中的所有的特殊字符串(类名、字段名和方法名、字段描述和方法描述符)是否符合格式
6.检查字节码的完整性(在”连接“过程中一次性验证字节码流,而非在程序执行的时候动态验证)

准备:
在准备阶段,Java虚拟机为类变量分配内存,设置默认初始值。在到达初始化之前,类变量没有被初始化为真正的初始值(在准备阶段不会执行任何Java代码的)。初始值的默认值:
这里写图片描述

解析:
解析过程就是在类型的常量池中寻找类、接口、字段和方法的符号引用。在这些符号引用替换成直接引用,这个过程可延迟到初始化之后,正在调用时运行。

初始化:
这个初始化过程就是程序员想要的初始化过程,把程序员设定的初始值赋值过来。
初始化一个类包含两个步骤:
1.如果类存在直接超类的话,且直接超类还没有被初始化,就先初始化直接超类
2.如果类存在一个类初始化方法,就执行此方法

超类总是在子类之前被初始化的。初始化接口不需要初始化它的父接口,所以只需要一个步骤:
1.如果接口存在一个接口初始化的地方,就执行此方法

所有的类变量初始化语句和类型的静态初始化器都被Java编译器收集在一起,放到一个方法中,只能Java虚拟机内部调用。
在下面一个图:
这里写图片描述
这里的执行顺序是根据程序顺序运行的。并非所有类都拥有一个方法,如果类没有声明任何类变量,也没有静态初始化语句,那么就没有方法。如果类声明了类变量,但是没有明确使用类变量初始化语句或者静态初始化语句初始化它们,那么也不会有。如果类仅包含静态final变量的类变量初始化语句,而且这些类变量初始化语句采用编译时常量表达式也不会有,比如:
这里写图片描述
接口跟这个一个道理。

那么主动使用和被动使用时什么?

使用一个非常量的静态字段只有当类或接口的确声明了这个字段才是主动使用。比如,类中声明的字段可能会被子类引用,接口中声明的字段可能会被子接口或者实现了这个接口的类引用。这时对于子类、子接口和实现了接口的类来说,这就是被动使用——不会触发它们的初始化。只有当字段的确是被类或者接口声明的时候才是主动调用。例:
这里写图片描述
这里写图片描述
上面的例子中,NewbornBaby类没有被初始化,也不需要被加载
这里写图片描述
如果一个字段既是static又是final的,并且使用一个编译时常量表达式初始化,使用这样的字段不是主动使用,例如:
这里写图片描述
这里写图片描述
可以看到Angry和Dog没有被初始化

讲完了类的生命周期,那么对象的生命周期又是什么?

实例化一个类有四种途径:
1.明确的使用了new操作符
2.调用Class或者java.lang.reflect.Constructor对象的newInstance()方法。
3.调用任何对象的clone方法
4.通过java.io.ObjectInputStream类的getObject方法反序列化

还有三种隐含的实例化。在类装载的过程中,对于Java虚拟机装载的每一个类型,他会暗中实例化一个Class对象来代表这个类型。其次,当Java虚拟机装载了在常量池中包含CONSTANT_String_info入口的类的时候,他会创建新的String对象的实例来表示这些常量字符串,还有一条通过执行包含字符串连接操作符的表达式产生对象,如下图:
这里写图片描述
其中隐含创建了3个String对象和一个StringBuffer对象,其中两个对象的引用传递到main()方法的args数组的一部分,另一个String对象代表arg[0]和arg[1]的连接,一个StringBuffer代表输出的文字部分

创建一个新实例会发生什么?
首先需要在堆中为保存对象的实例对象分配内存,一旦虚拟机为新的对象准备好了堆内存,它立即把实例变量的变量初始化为默认的初始值(不是程序员设置的,跟类变量连接中的准备一样)。一旦虚拟机完成了为新对象分配内存和实例变量赋默认初始值之后,随后就为实例变量赋正确的初始值(程序员设置的)。另外两种初始化方法:
1。如果对象是clone()调用来创建的,虚拟机把原来被克隆的实例变量中的值拷贝到新对象中。
2.如果对象是通过一个ObjectInputStream的readObject调用反序列发的。虚拟机通过从输出流中读入的值来初始化那些非暂时性的实例变量。否则虚拟机调用对象的实例初始化方法。
Java编译器为它编译的每一个类都至少生成一个实例初始化方法。在class文件中称为,如果类没有明确地声明任何构造方法,编译器默认产生一个无参数的构造方法,它仅仅调用超类的无参构造方法。
这里写图片描述
这里写图片描述

那么垃圾收集和对象的终结又是怎样的?
Java虚拟机必须实现具有自动堆存储管理策略——大部分采用垃圾收集器。程序可以明确或隐含地为对象分配内存,但是不能明确的释放内存。finalize终结方法最多一个对象只调用一次。

卸载类型?
如果程序不在引用某类型,那么这个类型就无法再对未来的计算过程产生影响。类型变成不可触及的,就可以被垃圾收集。
使用启动类装载器装载的类型是永远可触及的,所以永远不会被卸载。只有使用用户定义的类装载器装载的类型才会变成不可触及的,从而被虚拟机回收。如果某个类型的Class实例被发现无法通过正常的垃圾收集堆触及,那么这个类型就是不可触及的。
判断动态装载的类型Class实例在正常的垃圾收集过程中是否可以触及有两种方式:
1.如果程序保持对Class实例的明确引用就是可触及的
2.如果在堆中还存在一个可触及的对象,在方法区中它的类型数据指向一个Class实例,那么这个Class实例就是可触及的。从类型数据(也就是方法区),Java虚拟机必须能够确定对象的类,它的所有超类以及所有超接口的class实例

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值