首页
登录 | 注册

引用计数法的循环引用问题

 

  • 关于引用计数法,我们可以先看一段wiki上的描述:

     

    As a collection algorithm, reference counting tracks, for each object, a count of the number of references to it held by other objects. If an object's reference count reaches zero, the object has become inaccessible, and can be destroyed.

     

    When an object is destroyed, any objects referenced by that object also have their reference counts decreased.

     

    作为一种回收算法,引用计数法记录着每一个对象被其它对象所持有的引用数。如果一个对象的引用计数为零,那么该对象就变成了所谓的不可达对象,亦即可以被回收的。

     

    当一个对象被回收后,被该对象所引用的其它对象的引用计数都应该相应减少。

     

    而所谓的循环引用(circular referrence)有是什么意思呢?举个简单的例子:

     

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

     

    publicclassMyObject {

    publicObject ref =null;

    publicstaticvoidmain(String[] args) {

    MyObject myObject1 =newMyObject();

    MyObject myObject2 =newMyObject();

    myObject1.ref = myObject2;

    myObject2.ref = myObject1;

    myObject1 =null;

    myObject2 =null;

    }

    }

     

    从上面的代码可以轻易地发现myObject1与myObject2互为引用,我们知道如果采用引用计数法,myObject1和myObject2将不能被回收,因为他们的引用计数无法为零。引用计数法的循环引用问题

     

    但是具体是为什么呢?已上图为例,当代码执行完line7时,两个对象的引用计数均为2。此时将myObject1和myObject2分别置为null,以前一个对象为例,它的引用计数将减1。若要满足垃圾回收的条件,需要清除myObject2中的ref这个引用,而要清除掉这个引用的前提条件是myObject2引用的对象被回收,可是该对象的引用计数也为1,因为myObject1.ref指向了它。以此类推,也就进入一种死循环的状态。

    第二篇:

     

    微软将运行在公共语言运行时里的代码成为托管代码;但是从更广泛的意义上来说,只要语言提供了自动内存管理功能,我们使用其开发的代码都可以称为托管代码;自动内存管理即我们平时所说的垃圾回收器,垃圾回收器的实现是一个复杂的过程,其中涉及到很多的细节;垃圾回收器的难点并不是垃圾的回收过程,而是定位垃圾对象。当一个对象不再被引用的时候就可以被回收了,但是我们怎样才能知道其没有被引用呢?

     

    算法定义

     

    为每个对象增加一个字段记录被引用的次数,并由运行时跟踪和更新引用的总数;

     

    object p = new ComparableInt32(57);

     

    object q = p;

     

    我们实例化了一个对象ComparableInt32,并将其赋值给变量p,此时p引用了该对象,所以其计数器为1;然后我们又用p给变量q赋值,此时q也引用了该变量,所以其计数器变为2;如下图所示

     

    引用计数法的循环引用问题

    从上图我们可以看到,引用类型每次赋值都需要运行时更新计数器,运行时的更新代码可能如下

     

    if (p != q)

     

    {

     

    if (p != null)

     

    --p.refCount;

     

    p = q;

     

    if (p != null)

     

    ++p.refCount;

     

    }

    计数器算法的一大优势就是不用等待内存不够用的时候,才进行垃圾的回收,其可以在赋值操作的同时,检查计数器是否为0,如果是的话就可以立即回收;运行时的代码可能如下

     

    if (p != q)

     

    {

     

    if (p != null)

     

    if (--p.refCount == 0)

     

    heap.Release(p);

     

    p = q;

     

    if (p != null)

     

    ++p.refCount;

     

    }

     

    计数器算法的一大缺点就是不能解决循环引用的问题;如下图,我们构造了一个列表,我们将最后一个元素的next属性指向第一个元素,即引用第一个元素,从而构成循环引用;这个时候如果我们将列表的头head赋值为null,此时列表的各个元素的计数器都不为0,同时我们也失去了对列表的引用控制,从而导致列表元素不能被回收!

     

    引用计数法的循环引用问题

    算法特点

     

    1. 需要单独的字段存储计数器,增加了存储空间的开销;

     

    2. 每次赋值都需要更新计数器,增加了时间开销;

     

    3. 垃圾对象便于辨识,只要计数器为0,就可作为垃圾回收;

     

    4. 及时回收垃圾,没有延迟性;

     

    5. 不能解决循环引用的问题;

     

    http://blog.csdn.net/hou478410969/article/details/7530032

    第三篇:

     

    jvm区域总体分两类,heap区和非heap区。heap区又分:Eden Space(伊甸园)、Survivor Space(幸存者区)、Tenured Gen(老年代-养老区)。 非heap区又分:Code Cache(代码缓存区)、Perm Gen(永久代)、Jvm Stack(Java虚拟机栈)、Local Method Statck(本地方法栈)。

     

    HotSpot虚拟机GC算法采用分代收集算法:

     

    1、一个人(对象)出来(new 出来)后会在Eden Space(伊甸园)无忧无虑的生活,直到GC到来打破了他们平静的生活。GC会逐一问清楚每个对象的情况,有没有钱(此对象的引用)啊,因为GC想赚钱呀,有钱的才可以敲诈嘛。然后富人就会进入Survivor Space(幸存者区),穷人的就直接kill掉。

     

    2、并不是进入Survivor Space(幸存者区)后就保证人身是安全的,但至少可以活段时间。GC会定期(可以自定义)会对这些人进行敲诈,亿万富翁每次都给钱,GC很满意,就让其进入了Genured Gen(养老区)。万元户经不住几次敲诈就没钱了,GC看没有啥价值啦,就直接kill掉了。

     

    3、进入到养老区的人基本就可以保证人身安全啦,但是亿万富豪有的也会挥霍成穷光蛋,只要钱没了,GC还是kill掉。

     

    分区的目的:新生区由于对象产生的比较多并且大都是朝生夕灭的,所以直接采用标记-清理算法。而养老区生命力很强,则采用复制算法,针对不同情况使用不同算法。

     

    非heap区域中Perm Gen中放着类、方法的定义,jvm Stack区域放着方法参数、局域变量等的引用,方法执行顺序按照栈的先入后出方式。

     

    以上转自:http://lhc1986.iteye.com/blog/1421832

     

    以下转自:http://www.cnblogs.com/xhr8334/archive/2011/12/01/2270994.html

     

     


    GC工作机制 

    SUN的jvm内存池被划分为以下几个部分:

     

    Eden Space (heap)

     

    内存最初从这个线程池分配给大部分对象。

    Survivor Space (heap)

     

    用于保存在eden space内存池中经过垃圾回收后没有被回收的对象。

    Tenured Generation (heap)

     

    用于保持已经在survivor space内存池中存在了一段时间的对象。

    Permanent Generation (non-heap)

     

    保存虚拟机自己的静态(reflective)数据,例如类(class)和方法(method)对象。Java虚拟机共享这些类数据。这个区域被分割为只读的和只写的。

    Code Cache (non-heap)

     

    HotSpot Java虚拟机包括一个用于编译和保存本地代码(native code)的内存,叫做“代码缓存区”(code cache)。

    简单来讲,jvm的内存回收过程是这样的:

     

    对象在Eden Space创建,当Eden Space满了的时候,gc就把所有在Eden Space中的对象扫描一次,把所有有效的对象复制到第一个Survivor Space,同时把无效的对象所占用的空间释放。当Eden Space再次变满了的时候,就启动移动程序把Eden Space中有效的对象复制到第二个Survivor Space,同时,也将第一个Survivor Space中的有效对象复制到第二个Survivor Space。如果填充到第二个Survivor Space中的有效对象被第一个Survivor Space或Eden Space中的对象引用,那么这些对象就是长期存在的,此时这些对象将被复制到Permanent Generation。

     

    若垃圾收集器依据这种小幅度的调整收集不能腾出足够的空间,就会运行Full GC,此时jvm gc停止所有在堆中运行的线程并执行清除动作。

  • 以上是引用计数法的循环引用问题的内容,更多 计数法 引用 循环 问题 的内容,请您使用右上方搜索功能获取相关信息。


2020 jeepxie.net webmaster#jeepxie.net
10 q. 0.010 s.
京ICP备10005923号