对象生命周期
创建阶段
在对象创建阶段,系统要通过下面的步骤,完成对象的创建过程:
(1)为对象分配存储空间。
(2)开始构造对象。
(3)递归调用其超类的构造方法。
(4)进行对象实例初始化与变量初始化。
(5)执行构造方法体
下面是在创建对象时的几个关键应用规则:
(1)避免在循环体中创建对象,即使该对象占用内存空间不大。
(2)尽量及时使对象符合垃圾回收标准。
(3)不要采用过深的继承层次。
(4)访问本地变量优于访问类中的变量。
应用阶段
当对象的创建阶段结束之后,该对象通常就会进入对象的应用阶段。这个阶段是对象得以表现自身能力的阶段。也就是说对象的应用阶段是对象整个生命周期中证明自身“存在价值”的时期。在对象的应用阶段,对象具备下列特征:
◆系统至少维护着对象的一个强引用(Strong Reference);
◆所有对该对象的引用全部是强引用(除非我们显式地使用了:软引用(Soft Reference)、弱引用(Weak Reference)或虚引用(Phantom Reference))。
强引用:
只要引用存在,垃圾回收器永远不会回收
Object obj = new Object();
//可直接通过obj取得对应的对象 如obj.equels(new Object());
而这样 obj对象对后面new Object的一个强引用,只有当obj这个引用被释放之后,对象才会被释放掉,这也是我们经常所用到的编码形式。
软引用:
非必须引用,内存溢出之前进行回收,可以通过以下代码实现
Object obj = new Object();
SoftReference<Object> sf = new SoftReference<Object>(obj);
obj = null;
sf.get();//有时候会返回null
这时候sf是对obj的一个软引用,通过sf.get()方法可以取到这个对象,当然,当这个对象被标记为需要回收的对象时,则返回null;
软引用主要用户实现类似缓存的功能,在内存足够的情况下直接通过软引用取值,无需从繁忙的真实来源查询数据,提升速度;当内存不足时,自动删除这部分缓存数据,从真正的来源查询这些数据。
弱引用:
第二次垃圾回收时回收,可以通过如下代码实现
Object obj = new Object();
WeakReference<Object> wf = new WeakReference<Object>(obj);
obj = null;
wf.get();//有时候会返回null
wf.isEnQueued();//返回是否被垃圾回收器标记为即将回收的垃圾
弱引用是在第二次垃圾回收时回收,短时间内通过弱引用取对应的数据,可以取到,当执行过第二次垃圾回收时,将返回null。
弱引用主要用于监控对象是否已经被垃圾回收器标记为即将回收的垃圾,可以通过弱引用的isEnQueued方法返回对象是否被垃圾回收器标记。
虚引用:
垃圾回收时回收,无法通过引用取到对象值,可以通过如下代码实现
Object obj = new Object();
PhantomReference<Object> pf = new PhantomReference<Object>(obj);
obj=null;
pf.get();//永远返回null
pf.isEnQueued();//返回是否从内存中已经删除
虚引用是每次垃圾回收的时候都会被回收,通过虚引用的get方法永远获取到的数据为null,因此也被成为幽灵引用。
虚引用主要用于检测对象是否已经从内存中删除。
不可视阶段
在一个对象经历了应用阶段之后,那么该对象便处于不可视阶段,说明我们在其他区域的代码中已经不可以再引用它,其强引用已经消失,例如,本地变量超出了其可视范围,如下所示。
… …
public void process () {
try {
Object obj = new Object();
obj.doSomething();
} catch (Exception e) {
e.printStackTrace();
}
while (isLoop) { // ... loops forever
// 这个区域对于obj对象来说已经是不可视的了
// 因此下面的代码在编译时会引发错误
obj.doSomething();
}
}
… …
如果一个对象已使用完,而且在其可视区域不再使用,此时应该主动将其设置为空(null)。可以在上面的代码行obj.doSomething();下添加代码行obj = null;,这样一行代码强制将obj对象置为空值。这样做的意义是,可以帮助JVM及时地发现这个垃圾对象,并且可以及时地回收该对象所占用的系统资源。
不可到达阶段
处于不可到达阶段的对象,在虚拟机所管理的对象引用根集合中再也找不到直接或间接的强引用,这些对象通常是指所有线程栈中的临时变量,所有已装载的类的静态变量或者对本地代码接口(JNI)的引用。这些对象都是要被垃圾回收器回收的预备对象,但此时该对象并不能被垃圾回收器直接回收。其实所有垃圾回收算法所面临的问题是相同的——找出由分配器分配的,但是用户程序不可到达的内存块
可收集阶段、终结阶段与释放阶段
对象生命周期的最后一个阶段是可收集阶段、终结阶段与释放阶段。当对象处于这个阶段的时候,可能处于下面三种情况:
(1)垃圾回收器发现该对象已经不可到达。
(2)finalize方法已经被执行。
(3)对象空间已被重用。
类的生命周期
加载
在java中,我们经常会接触到一个词——类加载,它和这里的加载并不是一回事,通常我们说类加载指的是类的生命周期中加载、连接、初始化三个阶段。在加载阶段,java虚拟机会做什么工作呢?其实很简单,就是找到需要加载的类并把类的信息加载到jvm的方法区中,然后在堆区中实例化一个java.lang.Class对象,作为方法区中这个类的信息的入口
加载阶段是类的生命周期中的第一个阶段,加载阶段之后,是连接阶段。有一点需要注意,就是有时连接阶段并不会等加载阶段完全完成之后才开始,而是交叉进行,可能一个类只加载了一部分之后,连接阶段就已经开始了。但是这两个阶段总的开始时间和完成时间总是固定的:加载阶段总是在连接阶段之前开始,连接阶段总是在加载阶段完成之后完成。
连接
连接阶段比较复杂,一般会跟加载阶段和初始化阶段交叉进行,这个阶段的主要任务就是做一些加载后的验证工作以及一些初始化前的准备工作,可以细分为三个步骤:验证、准备和解析。
**验证:**
当一个类被加载之后,必须要验证一下这个类是否合法,比如这个类是不是符合字节码的格式、变量与方法是不是有重复、数据类型是不是有效、继承与实现是否合乎标准等等。总之,这个阶段的目的就是保证加载的类是能够被jvm所运行。
**准备:**
准备阶段的工作就是为类的静态变量分配内存并设为jvm默认的初值,对于非静态的变量,则不会为它们分配内存。有一点需要注意,这时候,静态变量的初值为jvm默认的初值,而不是我们在程序中设定的初值。jvm默认的初值是这样的:
基本类型(int、long、short、char、byte、boolean、float、double)的默认值为0。
引用类型的默认值为null。
常量的默认值为我们程序中设定的值,比如我们在程序中定义final static int a = 100,则准备阶段中a的初值就是100。
**解析:**
这一阶段的任务就是把常量池中的符号引用转换为直接引用。
那么什么是符号引用,什么又是直接引用呢?
连接阶段完成之后会根据使用的情况(主动引用还是被动引用)来选择是否对类进行初始化。
初始化
类的初始化过程是这样的:按照顺序自上而下运行类中的变量赋值语句和静态语句,如果有父类,则首先按照顺序运行父类中的变量赋值语句和静态语句。
1.public class Field1{
2. public Field1(){
3. System.out.println("Field1构造方法");
4. }
5.}
6.public class Field2{
7. public Field2(){
8. System.out.println("Field2构造方法");
9. }
10.}
1.class InitClass2{
2. static{
3. System.out.println("运行父类静态代码");
4. }
5. public static Field1 f1 = new Field1();
6. public static Field1 f2;
7.}
8.
9.class SubInitClass2 extends InitClass2{
10. static{
11. System.out.println("运行子类静态代码");
12. }
13. public static Field2 f2 = new Field2();
14.}
15.
16.public class Test2 {
17. public static void main(String[] args) throws ClassNotFoundException{
18. new SubInitClass2();
19. }
20.}
在类的初始化阶段,只会初始化与类相关的静态赋值语句和静态语句,也就是有static关键字修饰的信息,而没有static修饰的赋值语句和执行语句在实例化对象的时候才会运行。
使用
类的使用包括主动引用和被动引用
引用父类的静态字段,只会引起父类的初始化,而不会引起子类的初始化。
定义类数组,不会引起类的初始化。
引用类的常量,不会引起类的初始化。
1.class InitClass{
2. static {
3. System.out.println("初始化InitClass");
4. }
5. public static String a = null;
6. public final static String b = "b";
7. public static void method(){}
8.}
9.
10.class SubInitClass extends InitClass{
11. static {
12. System.out.println("初始化SubInitClass");
13. }
14.}
15.
16.public class Test4 {
17.
18. public static void main(String[] args) throws Exception{
19. // String a = SubInitClass.a;// 引用父类的静态字段,只会引起父类初始化,而不会引起子类的初始化
20. // String b = InitClass.b;// 使用类的常量不会引起类的初始化
21. SubInitClass[] sc = new SubInitClass[10];// 定义类数组不会引起类的初始化
22. }
23.}
卸载
该类所有的实例都已经被回收,也就是java堆中不存在该类的任何实例。
加载该类的ClassLoader已经被回收。
该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法。