垃圾回收比较核心的几个问题是
1.如何判定对象为垃圾对象?
可使用算法:引用计数法、可达性分析法
2.如何回收?
回收策略:标记-清除算法、复制算法、标记-整理算法、分代收集算法
垃圾回收器:Serial、Parnew、Cms、G1
3.什么时候回收?
本文学习引用计数算法
引用计数算法
在A对象(在堆内存中)中添加一个引用计数器,当内存中有地方引用这个A对象的时候,引用计数器的值就+1,当引用失效(例如:对象赋值为null)的时候,引用计数器的值就-1,当对象没有被引用的时候,引用计数器值为0,满足垃圾回收条件。
但是,使用此算法的比较少,因为对象循环引用的时候会有问题,如下图:A被B、C引用,A的引用计数器的值为2,B被A、D引用,B的引用计数器的值为2,当①、②两个引用断掉后,此时只剩下③、④两个引用,那么此时A、B的引用计数器的值都为1,不满足垃圾回收条件,不会被回收。但是很明显A,B除了彼此引用并无他用,也就说明这A、B现在就是垃圾对象。由于引用计数器的值都不为0,一直都不会被回收。就会出现问题。
上述的引用计数算法不常用,我们尝试通过代码模拟上述场景,通过程序调用看看JDK8中的垃圾回收器是否使用了引用计数器算法:如果demo1,demo2未被回收了,则证明JDK8垃圾回收使用了引用计数算法。反之未使用。
package cn.com.jvm.demo;
import java.util.ArrayList;
import java.util.List;
public class TestDemo {
//定义全局变量
private Object instance;
public static void main(String[] args) {
//定义两个对象
TestDemo demo1 = new TestDemo();
TestDemo demo2 = new TestDemo();
//让两个对象相互引用
demo1.instance = demo2;
demo2.instance = demo1;
//断掉外部引用
demo1 = null;
demo2 = null;
//调用垃圾回收器
System.gc();
}
}
在程序执行前我们先设置程序执行垃圾回收时打印垃圾回收信息
测试类右键→Run As→Run Configurations
设置打印参数-verbose:gc -XX:+PrintGCDetails
此时运行程序输出如下(回收前占用1474K内存,回收后占616K内存):
其实直接看不出来垃圾回收机制是否回收了我们想要回收的demo1,demo2对象,但是我们可以通过在对象的构造方法中,额外占用一些空间,再来查看垃圾回收信息
测试类中添加构造方法如下:
public TestDemo() {
//在执行构造方法时候,占用一块20M的内存空间
byte [] m = new byte[20 * 1024 * 1024];
}
再次执行程序,打印日志如下(回收前占用21954K内存,回收后占600K内存):
回收前的内存大小由1474K增长到21954K,回收后内存大小基本一致(616K~600K)由此可以看出demo1,demo2已经被回收,可见JDK8中的垃圾回收机制并未使用引用计数算法。