您的位置:首页 > 其它

垃圾回收——判断对象是否存活算法-引用计数法详解

2018-11-24 23:00 330 查看

垃圾回收首要的任务就是确定哪些对象是垃圾,哪些对象可进行回收,上节课我们也说过了,判定对象为垃圾对象的两种算法,一种是引用计数法,另一种是可达性分析法,我们本节课就来详细的了解一下什么是引用计数法。

引用计数法的思路是,在对象中添加一个引用计数器,当有地方引用这个对象的时候,这个引用计数器的值就加1,当引用失效的时候,这个引用计数器的值就减一。

那么,什么时候是引用失效呢?比如说把那个对象的值置为空,那么那个引用就失效了。

我们画个图来描述一下这个过程。首先,一块堆内存,一块栈内存

那么,我们创建了一个对象,那么,显然就是在栈内存中有一个引用指向堆内存中的一块对象区域,比如说直接指向它

当然我们之前也讲过了,有两种方式,一种是句柄,一种是直接地址,我们这里就简单的来说,就是直接指向了这个对象,这个时候这个引用计数器的值就会加1,就变成了1,那么,如果我们把这个引用的值置为空,即这个引用指向mull,那么,这个引用就失效了,那么,这个引用计数器的值就会减1,那么,这个时候引用计数器的值就为0

当垃圾回收器走到这里的时候,发现这个程序计数器的值为0,那么,这个对象就被回收掉了

这种方式是实现简单,而且,判定效率也是比较高的,但是,目前来讲,Java垃圾回收器基本上是没有使用这种算法的,那么,为什么没人使用呢?我们知道,其实只有在栈中引用指向堆中的时候才会有用,如果说,堆中的对象之间相互关联,没有对外的引用,外面找不着它了

这条线没了,那么

这一部分依然相当于垃圾,但是,发现,它们的引用计数器的值并不为零,所以垃圾回收器不会回收它们,那么,这就会出问题。我们可以来验证一下,我当前用的jdk,它所使用的垃圾回收器会不会回收像这种情况的垃圾。其实上面这个图的目的我们也都看明白了,就是说,这一块我们认定应该是垃圾,该被回收了,但是呢,通过引用计数法这种算法,它是回收不了这块区域的,那么,我们下面要做的是来验证,举一个例子,看看垃圾回收器能不能把这个回收掉。

我们想要看垃圾回收的这些日志信息,我们如何才能判断出来垃圾有没有被回收呢?我们需要打印出来垃圾回收器的日志信息,那么,如何打印出来垃圾回收器的回收日志呢?这里有两个参数,第一个参数是-verbose:gc,这个就是打印垃圾回收的日志信息,这个打印的是一个简单的信息,我们如果想看垃圾回收的详细信息的话,那么,还可以再加一个参数,-xx:+PrintGCDetail。-verbose:gc和-xx:+PrintGCDetail这两个参数就是能够打印详细的gc信息,通过这个日志信息,我们就可以有效的判断这个对象有没有被回收掉。那么这两个参数往哪里加?

那么,就开始写代码,如何测试上面说的内部之间有引用而外部没有引用的情况呢?我们就以这个Main为例子,我们创建两个它的实例

这是第一个对象,就相当于它指向它了

再来一个对象,因为我们要创建这边的

这边有三个,那么,我们举例时直接用两个就可以了

有了这两个对象之后,这两个对象之间要进行引用,这两个对象之间如何进行引用啊?重新画个图,给大家写清楚点,我们创建了两个对象,分别在栈内存中通过两个引用指向堆内存中的两块对象区域

现在先不谈引用计数器的事,我们现在要让堆内存中这两块对象区域循环引用

这种情况下,我们说,如果一旦这样了之后,这个计数器上面有两个引用

这个上面也有两个

然后,接着我们要干一件事,就是把它给断掉

把它也给断掉

那么,现在它上面有一个

它上面也有一个

那么,这个时候,其实

这两个对象已经变为垃圾了,但是,按照引用计数法来讲,它们的计数值并不为零,所以,垃圾回收器不回收它们,是这么一个过程,那么,我们写代码来验证的就是,垃圾回收器到底会不会回收这么一块区域

如果回收了,说明垃圾回收器使用的就不是引用计数法。代码中如何让m1指向m2,让m2指向m1呢?我们创建一个对象的引用

然后

这样就给

它们两个创建了循环的引用,这个循环引用创建完毕之后,下面我们要做的就是把

这两条线个断掉,怎么断呢?

这样就断掉了。下面,我们要让垃圾回收器去回收它们(即m1,m2),我们可以手动的调用一下System.gc();

写到这里,代码就写完了,我们来运行一下看一下效果。

这就是垃圾回收的一些日志信息,这一块的内容后面会详细的讲,那么,我们通过这些信息来看的话,其实也看不出来什么,其实

这里就是PSYoungGen这一块区域回收前的内存是1996k,回收后的内存是600k,但是从这里我们来看1996k变成了600k,确实回收了一部分内存,但是这个并不形象,我们如何才能形象的看到垃圾被回收掉了呢?其实这里1996k是哪里来的,我们并不知道,它可能就是一些地方所占用的内存,那么,我们手动的来给它加一些,让它多去占用一些空间,比如说我们在Main构造方法里面,我们开辟一块存储空间,占用一块内存,比如占用20M,一个byte是一个字节

我们认为,每创建一个Main对象,就会开辟20M这么大的一块空间,开辟了这么一块空间就得占用20M这么大一块内存,那么在进行垃圾回收的时候,这块内存就得被回收掉,再运行一下代码

我们发现的确大概是2M内存,最后,被回收了,也就是说,jdk8所采用的垃圾收集器,它进行验证对象是否还活着的依据并不是采用引用计数法,那么,采用的是什么方法呢?我们下节课讲。其实这个

垃圾日志信息,我们后面会具体的讲,我们可以通过PSYoungGen这个命名,我们可以看到,其实jdk8所采用的垃圾收集器是parallel,这是jdk8默认采用的一个垃圾回收器,当然,我们后面会具体的来讲垃圾收集器的一些内容。本节课就说到这里,大家一定要注意这两个参数

就是说,你如果不配置这两个参数,是打印不出来GC日志的。我们把这两个参数去掉

然后,再运行代码

我们发现直接就没有信息了,所以,那两个参数一定要加上。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐