对于Java两种确定对象为可回收的两种方式,予以记录!
内存中已经不再被使用到的空间就是垃圾
在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一;
任何时刻计数器为零的对象就是不可能再被使用的。那么这个对象就是可回收对象。
但是,在Java领域,至少主流的Java虚拟机里面都没有选用引用计数算法来管理内存,主要原因是,这个看似简单的算法有很多例外情况要考虑,必须要配合大量额外处理才能保证正确地工作,譬如单纯的引用计数就很难解决对象之间相互循环引用的问题。
如图,每一个对象的引用都是1,构成了循环引用,但是并不能被其他对象访问,这两个对象再无任何引用,引用计数算法也就无法回收它们。
在下列代码中,ReferenceCounting类是引用计数器类,用于记录对象被引用的次数。Object类是被引用的对象类,其中包含了一个ReferenceCounting对象。当创建对象时,引用计数加1,当移除对象引用时,引用计数减1。当引用计数为0时,表示对象不再被引用,可以执行回收操作。 在ReferenceCountingDemo类的main方法中,我们创建了两个对象obj1和obj2,分别增加和减少引用计数,演示了引用计数算法的基本原理
public class ReferenceCountingGC {
public static void main(String[] args) {
Object obj1 = new Object();
Object obj2 = new Object();
obj1.addReference();// obj1引用计数加1, 当前为2
obj2.addReference();// obj2引用计数加1, 当前为2
obj1.removeReference();// obj1引用计数减1
obj1.removeReference();// obj1引用计数减1, 计数为0,执行回收操作
obj2.removeReference();// obj2引用计数减1, 计数不为0,不执行回收操作
}
}
class Object {
private ReferenceCounting refCount;
public Object() {
refCount = new ReferenceCounting();
refCount.addReference();
}
public void addReference() {
refCount.addReference();
System.out.println("This Object " + this.hashCode() + ", refCount = " + refCount.getCount());
}
public void removeReference() {
refCount.removeReference();
System.out.println("This Object " + this.hashCode() + ", refCount = " + refCount.getCount());
if (refCount.getCount() == 0) {
System.out.println("This Object " + this.hashCode() + " is reclaimed.");
}
}
}
class ReferenceCounting {
private int count; // 引用计数器
public ReferenceCounting() {
count = 0;
}
public void addReference() {
count++;
}
public void removeReference() {
count--;
}
public int getCount() {
return count;
}
}
在下述案例中,我们创建了两个类A和B,它们分别有一个成员变量用于相互引用。在main方法中,我们创建了一个A对象和一个B对象,并通过setB和setA方法将它们相互引用起来。但是,由于它们之间存在循环引用,即A对象引用B对象,B对象引用A对象,导致它们的引用计数器都不会变为0,无法被回收。
尽管在最后我们将a和b设置为null解除了对它们的引用,但由于循环引用的存在,它们的引用计数仍然不为0,无法执行回收操作。
class A {
private B b;
public void setB(B b) {
this.b = b;
}
}
class B {
private A a;
public void setA(A a) {
this.a = a;
}
}
public class ReferenceCountingDemo {
public static void main(String[] args) {
A a = new A();
B b = new B();
a.setB(b);
b.setA(a);
a = null;
b = null;
System.gc();
}
}
从运行结果可以看到内存回收日志包含“Full GC (System.gc()) [PSYoungGen: 744K->0K(75264K)”,意味着虚拟机并没有因为这两个对象互相引用就放弃回收它们,这也从侧面说明了Java虚拟机并不是通过引用计数算法来判断对象是否存活的。
枚举根节点做可达性分析(根搜索路径)
这个算法的基本思路就是通过一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”(Reference Chain),如果某个对象到GC Roots间没有任何引用链相连,或者用图论的话来说就是从GC Roots到这个对象不可达时,则证明此对象是不可能再被使用的。
此对象到GCRoot对象没有引用链,此对象到GCRoot对象再也找不到一条可达路径
在可达性分析法中不可达的对象,它们暂时处于“缓刑阶段”,要真正宣告一个对象死亡,至少要经历两次标记过程;
可达性分析法中不可达的对象被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行 finalize 方法。当对象没有重写 finalize 方法,或 finalize 方法已经被虚拟机调用过时,虚拟机将这两种情况视为没有必要执行垃圾回收。
被判定为需要执行的对象将会被放在一个队列中进行第二次标记,除非这个对象与引用链上的任何一个对象建立关联,否则就会被真的回收。