类和对象的生命周期

 

1.类装载,连接,初始化

       java虚拟机通过装载,连接,初始化一个java类,使得该类可以被正在运行的java程序所使用。其中,装载就是把二进制形式的类型数据合并到虚拟机的运行时状态中,连接分为三个阶段-验证,准备,解析。验证确保了java类型数据格式正确并且适用于java虚拟机使用。准备则负责为该类型分配他所需要的内存,比如为他的类变量分配内存,解析则负责把常量池中的符号引用转化成直接引用。虚拟机的实现可以推迟解析这一步,它可以在当运行中的程序真正使用某个符号的时候再去解析它。当验证、准备和可选的解析都完成了以后,该类就为初始化做好了准备,在初始化期间,会给类变量赋以适当的初始值。

类的装载,连接,初始化需要顺序的执行,唯一的例外就是解析,他可以在初始化以后再行。

 

java虚拟机严格定义了类的初始化时机,所有的java虚拟机必须实现:必须在每个类或者接口首次主动使用时初始化,主要是以下六种情况:

(1)当创建某个类的新实例时(包括:new(),clone(),反射,反序列化)

(2)当调用某个类的静态方法(在字节码中执行invoke static的时候)

(3)使用某个类或者接口的静态字段,或者对该字段赋值时(在字节码中执行getstatic或者putstatic指令时),用final修饰的静态常量表达式除外

(4)当调用某些反射方法的时候,比如:Class中的方法,或者java.lang.reflect包中的类的方法。

(5)当初始化某个类的子类(一个类初始化的时候要求他的父类已经都被初始化完了)

(6)当虚拟机启动时,被标明为启动类的类(包含main方法的类,也是一个静态方法)

除了以上六种情况外,其他的所有的对类的使用都是被动的使用,都不会导致类的初始化。

 

对于任何一个类的初始化,都要求它的超类在它初始化之前就已经完成了初始化,但是,对于接口而言,这条规则并不适用。只有在某个接口所声明的非常量字段被使用时,该接口才会被初始化,它不会因为实现这个接口的子接口或者子类的初始化而初始化。(其实也好理解,接口字段都是static final类型,有的是编译时常量,只有对非常量的引用才会导致初始化)

1.1装载

装载分为三个基本的动作,要装在一个类型,java虚拟机必须要:

(1)通过该类型的完全限定名,产生一个代表该类型的二进制数据流

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

(3)创建一个表示该类型的java.lang.Class类对象

类装载步骤的最终产品就是这个Class对象,它是java程序和内部数据结构之间的接口。

类装载器并不需要一直等到某个类首次主动使用才会去载入它,java虚拟机规范允许类装载器缓存java类型的二进制表现形式,在预料到某个类型可能会被使用的时候就可以预先装载它。如果类装载器在预先装载时遇到缺失或者错误的class文件,它必须要等到程序首次主动使用这个类的时候才可以报错。如果这个类一直没有被主动使用,那么累装载器就一直不会报错。

1.2连接-验证

当装在完成以后,接下来就是连接。连接的第一个步骤就是验证:确认类型符合java的语义,且不会危及虚拟机的完整性。在验证的时候不同的虚拟机有很大的灵活性。

但是一般来说,首先要确保个各类之间的二进制兼容性:

(1)检查final类不能有子类

(2)检查final方法不能被覆盖

(3)确保类和超类之间没有不兼容的方法声明(比如:两个方法一摸一样,返回却不一样)

载入一个类要求他的超类都已经被载入,包括接口,初始化一个类要求他的父类都已经初始化了,但是接口不要求初始化一个子接口的时候,要初始化它的父接口,只是载入了,并不要求初始化。

在验证期间,这个类和他的所有的超类相互之间要二进制兼容,包括:

(1)检查所有的常量池入口相互之间的一致

(2)检查常量池中所有的特殊字符串(类名,字段名,方法名,字段描述符,方法描述符)是否符合格式

(3)检查字节码的完整性

1.3连接-准备

虚拟机载入了一个类,执行一些验证以后就进入了准备阶段。在这个阶段,java虚拟机会为类变量分配内存,设置默认的初始值。但在到达初始化阶段之前,类变量都没有被初始化为真正的初始值,准备阶段是不会执行java代码的。基本类型的默认值为0,boolean为false,引用类型为null。

1.4连接-解析

解析就是在类的常量池中寻找类,接口,字段和方法的符号引用,然后把这些符号引用替换成直接引用。

1.5初始化

为类变量赋以正确的初始值。

在java中,一个正确的初始值是类变量的初始化语句或者静态代码块语句给出的。

class Examplela{//类变量的初始化语句

    static int size=3*(int)(Math.random()*5.0);

}

class Examplelb{//静态代码块

    static int size;

    static{

        size=3*(int)(Math.random()*5.0);

    }

}

所有的类变量初始化语句和静态代码块都被java编译器收集到一起,放到一个特殊的方法中,对于类来说,这个方法叫做类的初始化方法,对于接口来说,被成为接口初始化方法。在类和接口的java class文件当中,这个方法叫做<clinit>。通常的java程序无法调用这个<clinit>方法,只是被虚拟机来调用,专门用来把静态变量设置为他们的正确的值。

初始化一个类包含两个步骤:

(1)如果类有超类,且还没有初始化,先初始化超类

(2)如果类存在初始化方法就执行这个方法。

而初始化接口并不需要初始化它的父接口,因此只需要看看是否有接口初始化方法,有就执行。

<clinit>()方法:

class Examplelc{

   static int width;

   static int height=(int)(Math.random()*2.0);

   static{

        width=3*(int)(Math.random()*5.0);

   }

}

<clinit>()方法首先执行Examplelc的唯一的一个类变量初始化代码,初始化了height,然后执行静态代码块,初始化了width,初始化是按照他们在源代码中出现的顺序来进行的。

并非所有的类都需要在他们的class文件中拥有一个<clinit>()方法,如果类没有声明任何的类变量,也没有任何的静态初始化块,就不会有<clinit>()方法。即使有类变量,但是并没有明确的使用类变量初始化语句或者静态代码块来初始化他们,那么类也不会有<clinit>()方法。只有static final常量表达式的也不会有<clinit>()方法。

下面是一个同时使用常量和其他类变量的例子:

class Exampleld{

    static final int angle=35;

    static final int length=angle*2;

}

Exampleld类被装载的时候,angle和length并没有作为类变量保存在方法区内,他们是常量,不是类变量,java虚拟机在使用它们的任何类的常量池或者字节码中直接存放的是他们的字面值。

class Examplele{

    static int symbolicRef = Examplela.size;

    static int localConst=Exampleld.length*(int)(Math.random()*3.0);//直接在字节码中嵌入70,而不存在符号引用

}

接口也可能在class文件中包含<clinit>()方法。接口当中的字段都是public static final,而且必须要在字段的初始化语句中进行初始化,如果接口中的字段不能被解析成一个编译时常量,接口就会拥有一个<clinit>()方法:

interface Examplele{

   int ketchup=0;

   int mustard=(int)(Math.random()*5.0);

}

 

 

 

关于方法区 

 

类的初始化:

java虚拟机严格定义了类的初始化时机,所有的java虚拟机必须实现:必须在每个类或者接口首次主动使用时初始化,主要是以下六种情况:

(1)当创建某个类的新实例时(包括:new(),clone(),反射,反序列化)

(2)当调用某个类的静态方法(在字节码中执行invoke static的时候)

(3)使用某个类或者接口的静态字段,或者对该字段赋值时(在字节码中执行getstatic或者putstatic指令时),用final修饰的静态常量表达式除外

(4)当调用某些反射方法的时候,比如:Class中的方法,或者java.lang.reflect包中的类的方法。

(5)当初始化某个类的子类(一个类初始化的时候要求他的父类已经都被初始化完了)

(6)当虚拟机启动时,被标明为启动类的类(包含main方法的类,也是一个静态方法)

除了以上六种情况外,其他的所有的对类的使用都是被动的使用,都不会导致类的初始化。

 

类的卸载:

  方法区即后文提到的永久代,很多人认为永久代是没有GC的,《Java虚拟机规范》中确实说过可以不要求虚拟机在这区实现GC,而且这区GC的“性价比”一般比较低:在堆中,尤其是在新生代,常规应用进行一次GC可以一般可以回收70%~95%的空间,而永久代的GC效率远小于此。虽然VM Spec不要求,但当前生产中的商业JVM都有实现永久代的GC,主要回收两部分内容:废弃常量与无用类。这两点回收思想与Java堆中的对象回收很类似,都是搜索是否存在引用,常量的相对很简单,与对象类似的判定即可。而类的回收则比较苛刻,需要满足下面3个条件: 

 

  1.该类所有的实例都已经被GC,也就是JVM中不存在该Class的任何实例。 

  2.加载该类的ClassLoader已经被GC。 

  3.该类对应的java.lang.Class 对象没有在任何地方被引用,如不能在任何地方通过反射访问该类的方法。 

 

  是否对类进行回收可使用-XX:+ClassUnloading参数进行控制,还可以使用-verbose:class或者-XX:+TraceClassLoading、-XX:+TraceClassUnLoading查看类加载、卸载信息。 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值