类的生命周期
jvm通过加载(loading), 链接(linking), 初始化(initialization)使一个类可以被运行中的程序使用. 加载就是把类的二进制形式的数据装进jvm; 链接就是安排组织这些二进制的数据使之达到运行时状态; 链接又分为三个子过程, 验证, 准备, 解析. 验证是保证类型的合法性; 准备是为该类型分配内存空间; 解析是把指向常量池的符号引用变成直接引用, 而事实上jvm对这一步可能采用延迟解析, 直到引用被真正使用. 初始化即给类变量合适的初始值.jvm规范对加载和链接的时机有很大灵活性, 但是对chushihau的时机给出严格规定: 必须在第一次主动使用该类的时候. 包括一下情形:
1 调用构造函数;
2 创建某类型的Array;
3 通过反射调用方法(不包括从父类继承的方法);
4 通过反射为该类申明的字段赋值(不包括从父类继承的).
加载(Loading): 类加载就是通过类加载器把.class二进制文件转入jvm的方法区,并在堆区创建描述该类的java.lang.Class对象,用来封装数据. JVM规范允许预先加载,在预料到某个类要使用的时候就加载这个类,如果这个类不存在,也不会马上抛出异常,只有在程序主动使用这个类的时候才会抛出异常,如果这个类一直没有使用,那么就永远不会抛出异常。
验证(Verification): 看类的合法性, 比如是否继承了final的类, 是否覆写了final方法, 是否实现了父类接口和抽象类定义的抽象方法.
准备(Preparation): 准备就是为静态成员分配内存空间,并设置默认值,byte int short long float 都是0,对象类型为null,boolean类型为false.
解析(Resolution): 解析就是从常量池定位所引用的类, 方法, 接口, 字段, 方法,并替换这些符号引用为直接引用. 如上面提到, 这步可能被延迟直到真正被使用.
初始化(Initialization): 即为类变量赋上合适的值, 有别于准备阶段赋的默认值,这里合适的值指的是程序员期望获得的值. static int size = 3 * (int) (Math.random() * 5.0); 在准备阶段的默认值是0, 在这一步, 它应该是一个通过随机产生的一个数. 每个class文件都有一个叫做"<clinit>"的初始化方法, 这个方法我们无法调用它, 但是他确确实实的存在, 有时候你会在一些抛出的异常中看到它的身影. 这个方法被jvm调用, 用来给类变量赋合适的值. 初始化一个类包含两步:
1 初始化直接父类(如果有的话), 如果它的父类还没有被初始化;
2 调用自己的初始化方法.
对象生命周期
(本图来自网络)
经过加载, 链接, 初始化, 这个类就可以被使用了. 程序可以存取它的static字段, 调用它的static方法, 或创建它的实例. 一个类有3种显式的途径被实例化: 通过new操作符, 调用class的newInstance方法, 调用已知对象的clone方法. 当实例化一个对象, 需有一些步骤:
1 jvm首先为对象分配存储空间, 包括对象的实例变量, 父类申明的变量, 以及一些与实现相关的组件的内存,如指向方法区class的指针;
2 一旦分配好空间, 即为变量赋默认值(byte int short long float 都是0,对象类型为null,boolean类型为false);
3 为变量赋合适的初始值, 即逻辑上,程序中应该出项的初始值;
4 调用初始化方法"<init()".
当一个对象不再被应用程序所引用, 则就可能被GC回收. 当对象被GC回收, 则它的生命周期结束, 它的内存被收回.