<<深入了解Javax虚拟机_JVM高级特性与最佳实践>>读书笔记(二)
判断对象是否需要被回收
-
引用计数算法
给每一个对象添加一个引用计数器,每当有一个地方引用时,计数器就加1 ,当引用失效的时候,计数器减1
**缺点:**无法解决对象之间相互循环引用的问题。
public class ReferenceCountGC { public Object instance = null; public static void main(String[] args) { ReferenceCountGC ob1 = new ReferenceCountGC(); ReferenceCountGC ob2 = new ReferenceCountGC(); ob1.instance = ob2; ob2.instance = ob1; ob1 = null; ob2 = null; System.gc(); }
上述代码,对象ob1和ob2都有字段instance,赋值令ob1.instance = ob2和ob2.instance = ob1,除此之外,没有其他引用,但实际上这2个对象已经不可能再被访问了,但他们因为互相引用这对方,他们的引用计数器都不是0,这也导致如果用引用计数算法的话,这2个对象将不被回收。
-
可达性分析算法
以“GC Roots”为起始点,从这些节点开始向下搜素,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,则可以证明这个对象不可用,就是可以被回收。
如图,object4、object5、object6 3个对象虽然互相有联系,但他们到GC Root是不可达,所以这3个对象是可以被回收。
可以作为GC Roots对象:
- 虚拟机栈(栈帧中的本地变量表)中的引用的对象
- 方法区中的类静态属性引用对象
- 方法区中常量引用的对象
- 本地方法栈中JNI(一般指Native方法)引用的对象
finalize方法
当经过上述可达性分析算法被判为不可达的对象时,不一定会被回收,只是处于带回收状态。一个对象要被回收,要经历2次标记,当用可达性分析算法算出改对象到GC Roots不可达的时候,被标记一下,并且判断该对象是否有必要运行finalize()方法(如果该对象没有重载finalize方法,或者已经执行过一次,都会被视为“没有必要执行”)。
如果被判断为有必要这行,那这个对象将会被放置到一个F-queue队列中,由虚拟机自动建立一个低优先级的Finalize线程来触发这个方法。然后GC会对F-queue中的对象进行二次标记,如果finalize方法中让对象与引用链上的一个对象建立关联,那么第二次标记就会被移出队列中,如果没有建立关联,就会被GC回收。
package com.coder.qiang.jdk;
/**
* @author gengqiang
* @date 2018/11/26
*/
public class FinalizeEscapeGc {
public static FinalizeEscapeGc SAVE_HOOK = null;
private void isAlive() {
System.out.println("yes, I'm staill alive");
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("finalize method executed");
FinalizeEscapeGc.SAVE_HOOK = this;
}
public static void main(String[] args) throws InterruptedException {
SAVE_HOOK = new FinalizeEscapeGc();
SAVE_HOOK = null;
System.gc();
Thread.sleep(500);
if (SAVE_HOOK!=null )
{
SAVE_HOOK.isAlive();
}else{
System.out.println("sorry! I'm dead!");
}
SAVE_HOOK = null;
System.gc();
Thread.sleep(500);
if (SAVE_HOOK!=null )
{
SAVE_HOOK.isAlive();
}else{
System.out.println("sorry! I'm dead!");
}
}
}
output:
finalize method executed
yes, I'm staill alive
sorry! I'm dead!
上述代码就验证了finalize方法执行后,没有被GC回收,但第二次,就被GC回收了。
垃圾收集算法
-
标记 - 清除(Mark - Sweep)算法
和名字一样,这个算法有2个阶段 标记,清除
缺点:
- 效率问题,标记,清除的效果都不高
- 空间问题,标记清除后回造成大量不连续的空间碎片,这就可能导致后续如果需要分配较大内存的时候,因为没有找到足够的连续内存回不得提前再次触发垃圾回收操作。
上图就是标记 - 清除算法的大概过程。
-
复制算法
将内存按容量划分为大小相等的两块,每次都使用一块,当一块用完,就将还存活的对象复制到另外一块,然后把已使用的内存空间一次性清理掉。这样每次都是对整个版区的内存回收,不用考虑内存碎片的问题了。
**缺点:**代价太大,需要讲内存缩小到一半。
上图就是复制算法的大概过程。
把内存分为不同区域,不同的区域的垃圾收集算法不一样。例如Java堆中分为新生代和老年代,新生代每次垃圾收集都会有大量对象被回收,所以一般都会用复制算法。而老年代因为存活率高,所以用标记 -清理 或者标记-整理算法来进行回收。
垃圾收集器
如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。
HotSpot虚拟机上的垃圾收集器: