对象在内存中的状态
Java 中所有的对象存放在堆区,一个对象根据它的引用状态分为三种状态:
- 可达状态:如果有一个以上的引用变量引用它则处于可达状态
- 可恢复状态:如果程序中某个对象不再有任何引用变量对它进行引用则进入了可恢复状态,在回收对象之前,系统会调用可恢复状态对象的 finalize() 方法进行资源的回收,如果系统在调用 finalize 方法的时候重新让一个引用变量指向了对象,则对象会恢复为可达状态,否则对象将会进入不可达状态。
- 不可达状态:如果对象进入了可恢复状态,并且调用了 finalize 方法后无法恢复到可达状态则进入了不可达状态,进入不可达状态的对象将会真正被系统回收。
finalize 方法
finalize 方法具有四个特点:
- 永远不要主动调用某个对象的 finalize() 方法,该方法应该交给垃圾回收机制调用
直接调用会报出 java.lang.NullPointerException 错误
- finalize() 方法何时被调用,是否被调用具有不确定性,不要把 finalize 方法当成一定会执行的方法
public class GCDemo {
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("正在被回收");
}
public static void main(String[] args) {
GCDemo demo = new GCDemo();
demo = null;
}
}
运行结果不会打印 “正在被回收”,执行 finalize 方法的线程优先级很低,所以不一定立即执行
- 当执行可恢复对象的 finalize 方法的时候,可能会使 finalize 对象重新变成可达状态
public class GCDemo {
private static GCDemo staticDemo = null;
private String tmp;
public GCDemo(String tmp) {
this.tmp = tmp;
}
public String getTmp() {
return tmp;
}
@Override
protected void finalize() throws Throwable {
super.finalize();
staticDemo = this;
System.out.println("正在被回收");
}
public static void main(String[] args) {
GCDemo demo = new GCDemo("tmp");
demo = null;
System.gc();
System.runFinalization(); //注释掉则会出现 java.lang.NullPointerException
System.out.println(staticDemo.getTmp());
}
}
这里在 finalize 方法中将 staticDemo 引用指向了可恢复状态的对象,所以导致最后的输出 tmp,证明释放掉的对象处于可达状态
- 当执行 finalize 方法出现错误的时候,垃圾回收机制不会报告异常,程序继续执行
throw new Exception("exception");
在 finalize 方法中添加抛出的语句会发现控制台没有输出
强制回收
虽然 finalize 方法调用的时机不确定,但是我们可以通过系统提供的 System.gc() 和 Runtime.getRuntime().gc() 进行强制回收,这种强制回收只是通知系统进行垃圾回收,但是是否回收依旧是不确定的。
对象引用的分类
对象的引用实际上分为下列四种:
- 强引用(StrongReference)
强引用是最常见的引用形式,程序创建一个对象并赋给一个引用变量,这就称之为强引用。只要一个对象存在强引用,它就不可能被系统回收。
String str = new String("demo");
- 软引用(SoftReference)
软引用通过 SoftReference 进行实现,对于一个软引用的对象而言,在系统内存不足的时候对象可能会被系统回收。
SoftReference sr = new SoftReference(str);
- 弱引用(WeakReference)
弱引用通过 WeakReference 实现,与软引用有点类似,只是引用级别更低,不管系统内存是否足够,总是会被回收。
WeakReference wr = new WeakReference(str);
- 虚引用(PhantomReference)
虚引用通过 PhantomReference 类实现,虚引用完全类似没有引用,虚引用对对象没有任何影响。虚引用主要用于跟踪对象被回收的状态,虚引用不能单独使用,必须和引用队列(ReferenceQueue)联合使用。
ReferenceQueue<String> rq = new ReferenceQueue<String>();
PhantomReference<String> pr = new PhantomReference<>(str, rq);