Java中类型的生命周期

一. java class 

  1.1 Java class的设计思想

  Java class文件采用字节编码,对一个Java类或者接口作全面描述,无论Java class文件在何种系统上产生,无论JVM运行在何种平台上,只要JVM能够读取和解释Java class文件即可,这种设计思想保证了Java的平台无关性

  1.2 Java class文件中包含JVM需要知道的关于类或者接口的所有信息

 

二. 类的生命周期

  2.1 JVM通过装载,连接和初始化一个Java类型,使该类型可以被正在运行的Java程序所使用,当该类型不再被使用时,GC会回收它所占用的内存。

  2.2 在类和接口被装载和连接的时机上,JVM的规范给JVM实现提供了一定的灵活性,也就是说,JVM并没有规定类和接口什么时候被加载,什么时候被连接,但是JVM规范严格规定了类和接口初始化的时间,所有的JVM的实现必须在每个类或者接口首次主动使用时初始化,有六种情况下,类和接口被主动使用

  (1)创建某个类的实例(new 反射 克隆 反序列化)

  (2)调用类的静态方法(接口中没有静态方法,接口中的方法都是public abstract的)

  (3)使用类或者接口的静态字段

  (4)使用反射机制

  (5)初始化类的子类会让初始化父类,但是这点不适用于接口

  (6)启动类

   对于接口来说,初始化它的实现类或者它的子接口,不会导致接口被初始化,接口被初始化,只有一种情形,那就是使用接口中的static变量

   JVM的实现可以在很早的时候就把class加载到JVM中,不需要等到类或者接口首次主动调用这个类或者接口,但是加载过程如果出现错误,并不会检查出来,只有在类或者接口初始化的时候才会检查出来

  2.3 装载

    装载阶段的三个动作

     (1)根据类的全限定名,找到类的二进制数据流;

     (2)解析这个二进制数据流为方法区的内部数据结构;

     (3)创建一个java.lang.Class对象,代表该类型。

    2.3.1 JVM会解析class的二进制流并解析成方法区的内部数据结构,装载步骤的最终产品是这个Class类的实例对象(就代表这个类),它是Java程序与内部数据结构之间的接口,如果Java程序要访问类型的信息(存放在内存运行时数据区的方法区中),Java程序就要调用该类型对应的Class实例对象的方法(类型的Class实例就代表这个类型)

    2.3.2 当一个接口的实现类或者子接口被初始化时,该接口不会被初始化,但是,当实现了父接口的子类(或者是扩展了父接口的子接口)被装载时,父接口必须被装载,他们不会被初始化,只是被装载了。

   2.4 连接

          2.4.1 验证

          2.4.2 准备

            a. 在准备阶段,JVM为类分配内存,也就是为类变量分配内存,并根据类型设置成为默认的初始值,在到达初始化阶段之前,JVM不会执行Java代码,因此类变量不会被初始化为真正的初始值。

            b.在准备阶段,JVM可能会为一些特殊的数据结构分配内存,比如对象的方法表(方法表中的每一项都是指向方法区中方法数据的指针),在堆中对象有指向方法表的指针。

         2.4.4 解析

             在类被装载的整个过程中,只有解析是可选的,解析的对象是内存运行时数据区的方法区中的符号表,在加载过程中符号表中保存的是符号引用,解析的过程是将符号表中的符号引用用直接引用替代,这一步可以在类初始化之后完成,即在JVM执行java代码时进行动态解析。

      2.5 初始化

         为了让类或者接口被Java程序使用,在类或者接口首次主动调用时,需要完成的最后一个步骤是类的初始化操作,即将类变量赋予正确的初始化值,在初始化阶段,JVM会执行初始化命令,类的初始化命令包括类变量初始化语句或者静态初始化语句(也就是静态代码块)

      2.5.1 初始化一个类一般需要两个步骤

         a.  初始化类的直接超类

         b.  执行初始化方法《Java class中的<clinit>方法》

       但是初始化接口只需要一个步骤,就是执行接口的初始化方法《class文件中的<clinit>方法》

     2.5.3 因为类变量存放在内存运行时数据区的方法区中,这部分是一个JVM进程中的所有线程所共享的,因此类的初始化(即对类变量的操作)需要保证线程同步,当一个线程执行类的初始化操作时,其它线程需要等待

     2.5.4 并非所有的类都需要在它们的class文件中拥有一个<clinit(初始化)>方法,如果类没有声明任何类变量,也没有静态初始化语句,它就不会有<clinit>方法;(2)如果声明了类变量,但是没有明确使用类变量的初始化语句或者静态代码块来初始化它们,也不会有<clinit>方法,如果类中仅包含静态final变量的类变量初始化语句,这些类变量初始化语句采用编译时常量表达式,类也不会有<clinit>方法,只有需要执行java代码(通过执行Java代码进行初始化操作)赋予类变量正确初始值的类才会有类初始化方法<clinit>

    2.5.5 只有类或者接口被首次主动调用时才会对它们进行初始化,一个类声明的字段可能会被子类引用,接口中声明的字段可能会被子接口或者实现这个接口的类引用,对于子类,子接口和实现了接口的类来说,引用这些字段是被动引用,使用它们不会触发他们的初始化,只会触发声明它们的类或者接口的初始化(这是因为类型的方法区表示中只保存了该类型中定义的类变量,并没有保存其父类型中的类变量,因此,类变量在哪个类型中定义,使用该类变量时,就会初始化哪个类型)。

 

  三。对象的生命周期

       3.1 对象的显式实例化

         和类一样,如果Java程序要使用对象,需要对类进行实例化,实例化一个类有四种途径:new  调用Class对象的newInstance()方法  调用现有对象的clone()方法  通过java.io.ObjectInputStream类的getObject()方法反序列化

       3.2 对象的隐式实例化

         main()方法的参数会隐式实例化String对象

         加载类时创建的java.lang.Class对象是对类的隐式实例化

          对两个字符串使用连接操作符

       3.3 创建一个类的新实例时,无论是显式创建还是隐式创建,首先都需要在堆区中为对象分配内存,和为类在方法区分配内存不同的是,类的字段仅包含在类中声明的类变量,不包扩在超类中声明的类变量(因此,通过子类引用超类的类变量只会导致超类被初始化,因为该字段保存在超类的方法区内存表示中),而堆区中对象的内存表示中,回味对象类型的实例变量和所有超类类型的实例变量分配内存空间

        3.4 在分配内存之后,会对实例变量进行初始化操作

             clone而言,直接复制对象的实例变量值给新对象的实例变量

             反序列化,getInputObject()从输入流中提取数据进行初始化

             其他,调用构造方法

         

        3.5 在一个对象不再被Java程序所引用时,需要回收该对象的内存空间,Java程序可以明确地为对象分配内存,但是不能明确地回收对象的内存,JVM也没有相应的指令提供内存回收功能,JVM规范只是规定在堆区为对象分配内存空间,JVM具体实现可以根本不释放对象的内存,但是,比较好的策略是当对象不再被引用时,让GC线程回收对象的内存。

        3.6 终结方法,GC回收对象的内存空间之前会执行对象的终结方法,终结方法可能使对象重新被引用,但是,GC只能执行同一个对象的终结方法一次,对象再次进入可恢复状态时,GC不会再执行终结方法了

         3.7 JVM创建并初始化类的对象,这样Java程序可以使用这些对象,当对象不再被使用时(即不再被引用)时,GC要回收这些对象。同样,对于类而言,对类的加载,连接和初始化之后,会在方法区为类分配内存,当类不再被使用时,应该释放他们在方法区的内存,有GC进行内存回收。

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值