精华内容
下载资源
问答
  • jvm GC引用计数

    2019-01-07 17:46:07
  • 转载:http://www.cnblogs.com/hzzjj/p/6268432.html
    展开全文
  • GC引用计数算法

    2019-09-25 18:38:24
    我们都知道c++程序在...引用计数算法是为每个对象一个计数器用来保存对象被引用的次数,如果该对象被其他对象引用,计数器加1,对该对象引用结束则计数器减1,当计数器为0时认为该对象已经没有任何引用,就会被回...

    我们都知道c++程序在创建对象时需要构造函数为对象开辟空间,该对象不再使用时需要手动写析构函数释放空间。但Java因为实现了垃圾回收机制,程序员不需要手动的去释放对象空间。

    不同的厂商采用不同的算法实现GC

    引用计数算法

    引用计数算法是为每个对象一个计数器用来保存对象被引用的次数,如果该对象被其他对象引用,计数器加1,对该对象引用结束则计数器减1,当计数器为0时认为该对象已经没有任何引用,就会被回收。

    String str = new String("gc");
    str = null;
    

    在这里插入图片描述
    在这里插入图片描述
    如图,当str设置为null时,gc的引用次数为0,将被回收

    但引用计数法会出现循环依赖问题

    public  class  MyClass {
    	public Object instance;
    }
    
    MyClass c1 = new MyClass();
    MyClass c2 = new MyClass();
    
    c1.instance = c2;
    c2.instance = c1;
    
    c1 = null;
    c2 = null;
    

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    此时两个instance互相引用,计数器一直为1,GC永远无法回收它们。

    展开全文
  • GC-引用计数

    2019-04-08 21:16:19
    GC-引用计数GC:释放怎么都无法被引用的对象的机制。 引用计数法在对象的元数据区,加入了计数器,用于表示当前对象被引用了多少次。 引用计数法与其他GC算法相比,在于GC的的调用时机:GC标记-清除算法是在没有...

    GC-引用计数法

    GC:释放怎么都无法被引用的对象的机制。

    引用计数法在对象的元数据区,加入了计数器,用于表示当前对象被引用了多少次。
    在这里插入图片描述
    引用计数法与其他GC算法相比,在于GC的的调用时机:GC标记-清除算法是在没有可用分块时,会调用GC函数,进行垃圾处理回收,即是一种无侵入式的GC,这样就造成,最大暂停时间较大,此外,标记-清除算法即是产生了垃圾,也不会将其马上回收,只会在没有分块的时候,将垃圾一并回收;GC引用计数法是一种侵入式算法,其最大暂停时间表现较好,比较均匀,内存管理和mutator同时运行是引用计数法的一大特征。

    在引用计数法中,只有两种,连接到空闲链表的对表,其余都是活动对象。引用计数法中,对象的状态监控是实时的。

    GC-引用计数法的主要内容:

    new_obj(size){//创建对象,分配内存块
        obj=pickup_chunk(size,$free_list)
        if(obj==NULL)
            allocation_fail()
        else
            obj.ref_cnt=1
            return obj
    }
    update_ptr(){ //mutator,引用改变
        inc_ref_cnt(obj)//计数增量
        dec_ref_cnt(*ptr)//计数减量
        *ptr=obj
    }
    //先增量,再减量,主要原因是解决obj和ptr指向同一对象的问题,若先减量,则该对象
    //可能已经成为垃圾,从而造成bug
    inc_ref_cnt(obj){
        obj.ref_cnt++
    }
    dec_ref_cnt(obj){
        obj.ref_cnt--
        if(obj.ref_cnt==0)//没有引用了,即是垃圾了,遍历子对象,进行减量操作
            for(child:children(obj))
                dec_ref_cnt(*child)
            reclaim(obj) //回收
    }
    

    优点:可即刻回收垃圾,当对象计数为0时,会立刻回收;最大暂停时间短,mutator更新指针时才会执行垃圾回收,是一种侵入式,可有效消减最大暂停时间,即将该时间分散到了指针更新时;没有必要沿指针查找,不会像标记-清除算法一样从跟开始遍历。

    在分布式环境中,如果要沿各个计算节点之间的指针进行查找,成本就会增加,就需要极力控制沿指针查找的次数,所以有一种做法是,在各个计算节点内回收垃圾使用GC垃圾-清除算法,在考虑到节点间的引用关系时则采用引用计数法。

    缺点:计数器值的增减处理繁重,只要更新指针,就会进行计数器更新,所以值的增减处理会变得繁重;计数器需要占用很多位,32位机器,可能要让2的32次方个对象同时引用一个对象,考虑这种情况,就需要32位大小,这会使内存空间的使用效率降低;实现繁琐复杂,因为该算法是侵入式的,即需要把*ptr=obj全部更换成update_ptr(ptr,obj);循环引用无法回收,每个对象都有引用计数,就无法回收,但其本身已经是垃圾了。

    class Person{
        string name
        person laver
    }
    A=new Person("A")
    B=new Person("B")
    A.lover=B //循环引用
    B.lover=A
    A=NULL //对于根指针,没有对象引用,已经是垃圾了
    B=NULL
    

    针对GC-引用计数法的一些缺点,进行相关改进:延时引用计数法可解决计数器增减繁重的问题;Sticky引用计数法,可一降低计数器的宽度,提高空间利用率;1位引用计数法,是一种特殊的Sticky引用计数法,使用指针的的后两位作为计数器;部分标记-清除算法,可解决循环引用问题。

    延迟引用计数法:对引用计数延迟操作

    指针引用对象变化,不会立即更新到,这将引用变化更新推迟,推迟到空闲链表中没有满足的分块,同一进行扫描更新。具体体现在将upate_ptr(ptr,obj)ptr,obj)改为*ptr=obj,也就是计数更新只在创建对象时。
    延迟引用计数法,引入一个ZCT(Zero Count Table)表,会事先记录下计数器值在dec_ref_cnt()函数的作用下变为0的对象。之后会扫描该表,更新计数,更新方法同标记-引用算法,从根开始递归扫描。
    在这里插入图片描述

    计数器值为0的对象不一定都是垃圾?因为该改进算法,只在对象生成是计数器+1,之后的引用,并没有更新,所以为0,不一定是垃圾,记录到zct表中,后续遍历确认。

    dec_rec_cnt(obj){
        obj.ref_cnt-- 
        if(obj.ref_cnt==0)//表中记录可能是垃圾的对象
            if(is_full($zct)==TRUE)
                scan_zct()//表满时,进行垃圾扫描和回收
            push($zct,obj)
    }
    new_obj(size){
        obj=pickup_chunk(size,$free_list)//没有满足的分块
        if(obj==NULL)
            scan_zct()//扫描zct表,清理垃圾
            obj=pickup_chunk(size,$free_list)//再次申请分块,主要是回收垃圾获取的分块
            if(obj==NULL)
                allocation_fail()
            
        obj.ref_cnt=1 
        //mutator不会进行计数更新,只在创建对象时,有个标记1,所以需要zct表扫描。
        return obj
    }
    
    scan_zct(){
        for(r:$roots) //所有活动对象,计数增加;从这看,计数器的宽度问题还在
            (*r).ref_cnt++
        
        for(obj:$zct)//检查zct表,遍历之后,计数仍未0,则该对象真的是垃圾了
            if(obj.ref_cnt==0)
                remove($zct,obj)//回收垃圾
                delete(obj)     
        for(r:$roots) //恢复之前的引用计数
            (*r).ref_cnt--
    }
    delete(obj){
        for(child:children(obj))//将垃圾对象中的子对象的引用减量
            (*child).ref_cnt--
            if((*child).ref_cnt==0)
                delete(*child)
        reclaim(obj)
    }
    

    优点:延迟了根引用的计数,将垃圾一并回收,通过延迟,减轻了因根频繁变化而导致的计数器增减所带来的额外负担。
    缺点:延迟计数器值增减,致使垃圾不能马上回收,并压迫堆,也就失去了引用计数法的一大优点–可即刻回收垃圾;另scan_zct()函数导致最大暂停时间延长。

    Sticky引用计数法:对计数器的改进处理
    其解决的是计数器占用空间过大的问题,比如采用5位的计数器,这会带来计数溢出问题,通过增加对计数器的管理,来解决该问题;此外,之所以可以采用该问题,是因为实际中极高引用数的情况很少,一般引用数不大,甚至大多对象创建,之后就释放了。

    计数器溢出处理:

    1. 什么也不做,溢出就溢出了,也就无法回收该对象,正如之前所说,因为溢出的对象较少,另外,溢出的对象在程序中占有较高地位,成为垃圾的可能性也较低,所以问题也不大…但终究是有问题的。
    2. 使用GC标记-清除算法进行管理,该后援会刷新对象的引用计数,即活动对象仍有引用,垃圾对象引用数为0,这保证可以回收之前引用溢出无法回收的垃圾;并且该后援可以处理循环引用问题,因为遍历根了嘛,不在根下,计数被置0后,没有增长,即是垃圾。但标记-清除算法是要花费更多时间的,所以吞吐量也会相应减少。

    GC标记-清除算法管理引用计数:

    1. 一开始就把所有对象的计数器值设为0
    2. 不标记对象,而是对计数器进行增量操作
    3. 为了对计数器进行增量操作,算法对活动对象进行了布置一次的搜索
    mark_sweep_for_counter_overflow(){
        reset_all_ref_cnt()//将计数清零,方便处理计数溢出的对象
        mark_phase() //更新引用计数
        sweep_phase() //回收垃圾
    }
    mark_phase(){//dfs方式搜索
        for(r:$roots) //所有活动对象入栈,以更新其计数
            push(*r,$mark_stack)
           
        while(is_empty($mark_stack)==FALSE)
            obj=pop($mark_stack)
            obj.ref_cnt++ //更新计数
            if(obj.ref_cnt==1) //每个对象的子对象只遍历一次,就是扫描整个树一次
                for(child: children(obj))//若被引用,还会入栈
                    push(*child,$mark_stack)
    }
    sweep_phase(){
        sweeping = $heap_top
        while(sweeping<$heap_end)//还是通过遍历对象,回收的,费时
            if(sweeping.ref_cnt==0)//回收垃圾
                reclaim(sweeping)
            sweeping+=sweeping.size
    }
    

    1位引用计数法:

    该算法更激进了些,是Sticky引用计数法的特殊情况,其作者认为,大多对象,在创建后,就会立马死亡,所以可以只用1位计数器。
    这里的计数器称为tag更为合理,这里对象只有两种情况:0-一定不是垃圾(UNIQUE),1-可能是垃圾(MULTIPLE)。

    指针通常默认4个字节对齐,所以没法利用低2位,可以利用该性质,就能拿出1位作为计数tag,用作内存管理。为什么指针4b对齐,没法利用低2位?
    经询问本科时的师兄,指针指向的是数据是4B对齐,指针变化都是4倍进行的,所以最低两位,都是0。这里吐槽一下该书!
    这里用这个特性,就要求系统数据必须4B对齐。巧是巧,但就是对系统有要求,反而不好。

    1位引用计数法也是在更新指针的时候进行内存管理的,不过它不像以往那样要引用对象来更新指针,而是通过复制某个指针来更新指针。
    次改进,只需将mutator的update_ptr()更换成copy_str()即可。

    copy_ptr(dest_ptr,src_ptr){
        //原对象丢失一个引用,
        //之前状态unique或者multiple,若是unique则可进行回收
        delete_ptr(dest_ptr)
        *dest_ptr=*src_ptr
        set_multiple_tag(dest_ptr)//此时一定是multiple
        if(tag(src_ptr)==UNIQUE)
            set_multiple_tag(src_ptr)
    }
    delete_ptr(ptr){
        if(tag(ptr)==UNIQUE)//回收
        reclaim(*ptr)
    }
    

    优点:不容易出现告诉缓存缺失,应该是不会像标记-清除算法那样遍历对象,就会造成较多的不在缓存中的对象,从而访问内存,增加时间;对象元数据不需要计数器,所以节省内存空间。
    缺点:跟Sticky引用计数算法基本一样,在1位计数器下,可能出现大量对象计数器溢出的情况,这会给堆带来较大负担,即垃圾压迫堆。

    部分标记-清除算法:引用计数的后援,负责循环引用垃圾的处理

    该算法针对之前的通过标记-清除算法要进行全部搜索的后援,进行了优化,提出一中部分标记-清除算法,将垃圾限定可能产生垃圾的对象集中。
    这里定义了四种颜色,占用2位空间:

    1. 黑:绝对不是垃圾的对象(对象产生时的初始颜色)
    2. 白:绝对是垃圾的对象
    3. 灰:搜索完毕的对象
    4. 阴影(hatch):可能是循环垃圾的对象

    该算法是较为复杂的,可通过如下图示理解,其思想:
    0. 对于循环引用,环节点就在外部引用那个点,即其计数是大于2的,所以通过遍历确定外部引用的节点,已被断开即可。

    1. 确定方法就是颜色标记法,有对象释放,引用减少,其就可能时循环引用的节点,标记为阴影,并不马上确定是否时循环引用垃圾;
    2. 在空闲链表上的分块无法满足时,就会启动该标记-清除算法,扫描所有阴影对象,这里要进行多次扫描,因为颜色要经历几次变化,首先从阴影到灰色,并对子对象的引用数减量;知道所有的阴影对象便会灰色;
    3. 接着,再次遍历hatch_queue,这时都是灰色的,根据其引用数,确定是否是白色,引用数为0,则是垃圾,否则是活动对象,置为黑色。
      注意这里是减量是对其子对象操作的,若是环,则减量操作会作用到最终的起点上,并形成一个环的逻辑,为什么不对该对象本身减量,见bad_paint_gray(obj)。

    在这里插入图片描述

    在这里插入图片描述
    在这里插入图片描述

    dec_ref_cnt(obj){
        obj.ref_cnt--
        if(obj.ref_cnt==0)
            delete(obj)
        else if(obj.color!=HATCH)
            obj.color=HATCH
            enquere(obj,$hatch_queue)
    }
    new_obj(size){
        obj=pickup_chunk(size)
        if(obj!=NULL)//新创建的对象是黑色的
            obj.color=BLACK
            obj.ref_cnt=1
            return obj
        else if(is_empty($hatch_queue)==FALSE)
            scan_hatch_queue()//分块失败,则要扫描,清理垃圾
            return new_obj(size)
            //递归进行的,扫描一次不行么?应该可以,这里也只需一次递归吧,
            //再次递归,scan_hatch_queue,没有变化啊,该处理的已经处理完了
        else
            allocation_fail()
    }
    scan_hatch_queue(){
        obj=dequeue($hatch_queue)
        if(obj.color==HATCH)//或是嘤嘤的,则要进行判断,是否是循环引用垃圾
            paint_gray(obj)//标记访问过的
            scan_gray(obj)//扫描访问过的,并确定出白色,即垃圾
            collect_white(obj)//回收垃圾
        else if(is_empty($hatch_queue)==FALSE)
            scan_hatch_queue()//递归进行,将hatch_queue处理完
    }
    paint_gray(obj){
        if(obj.color==(BLACK|HATCH))
            obj.color=GRAY
            for(child:children(obj))
            //减量操作在子对象上,这样可以表示循环,a->b,b->a,最后操作在自己本身,可以确定是循环
            //若对自己减量,则无法区别,a->b,b->c 和a->b,b->a
                (*child).ref_cnt--
                paint_gray(*child)
    }
    scan_gray(){
        if(obj.color==GRAY)
            if(obj.ref_cnt>0)//活动的对象,其子对象多是活动的
                paint_black(obj)
            else //循环中的垃圾,回收
                obj.color=WHITE
                for(child:children(obj))//遍历子对象,将垃圾标为白色,活动对象,置黑,待下回扫描
                    scan_gray(*child)
    }
    paint_black(obj){//将活动对象标黑
        obj.color=BLACK
        for(child:children(obj))
            (*child).ref_cnt++
            if((*child).color!=BLACK)
                paint_black(*child)
    }
    collect_white(obj){//对白色对象的子对象进行减量操作,并回收白色对象
        if(obj.color==WHITE)
            obj.color=BLACK
            for(child:children(obj))
                collect_white(*child)
            reclaim(obj)
    }
    
    

    扫描hatch_queue时,为什么对子对象减量,而不是本身,见如下代码
    减量操作在子对象上,这样可以表示循环,a->b,b->a,最后操作在自己本身,可以确定是循环;若对自己减量,则无法区别,a->b,b->c 和a->b,b->a。可见下图的例子。

    bad_paint_gray(obj){
        if(obj.color==(BLACK|HATCH))
            obj.ref_cnt--
            obj.color=GRAY
            for(child:children(obj))
                bad_paint_gray(*child)
    }
    

    在这里插入图片描述
    评价:部分标记-清除算法因为从队列搜索对象所付出的成本太大了,队列中的对象是候选垃圾,所以搜索的对象不在少数;且因为颜色标记,所以需要遍历3次(mark_gray、scan_gray、collect_white),所以这也大大增加内存管理时间,即引用计数法的一大优点,最大暂停时间不复存在了。

    以上内容为读书笔记,参考资料《垃圾回收的算法与实现》。

    展开全文
  • GC引用计数

    2017-01-05 10:26:12
    问题:垃圾回收机制中,引用计数法是如何维护所有对象引用的? 作者:RednaxelaFX 链接:https://www.zhihu.com/question/21539353/answer/18596488 来源:知乎 著作权归作者所有,转载请联系作者获得授权。 ...
  • GC引用计数

    2020-12-31 11:07:07
    引用计数算法是垃圾回收器中的早起策略,在这种方法中,堆中的每个对象实例都有一个引用计数器,当一个对象被创建时,且该对象实例分配给一个变量,该变量计数设置为1,当任何其他变量赋值为这个对象的引用时,计数...
  • 作者:Android面试官引用计数方式 GC 存在循环引用问题,导致无法辨别无用对象,而 GC ROOT 方式不存在循环引用的问题引用计数GC ROOT 的实现机理很易理解,面试时大家都能流利应答,那怎么才能脱颖而出呢?...
  • GC算法-引用计数

    2020-04-05 18:37:17
    GC算法-引用计数法 概述 引用计数法又是什么鬼呢? 顾名思义, 对对象的引用进行计数. 通过记录每个对象被引用的次数, 来确定这个对象是否可以被回收. 实现 首先, 对对象的引用数量进行管理, 什么时候会更新呢? 创建...
  • JVM详解-GC-引用计数

    2020-11-07 19:13:35
    引用计数法 1. GC概述 GC:垃圾回收机制 作用区域: JVM在进行GC时,并不是对堆中的三个区域(新生代、幸存区、老年区)进行统一回收。大部分时候,回收都是在新生代区域。 新生代 幸存区:from 、to 老年...
  • 引用计数方式 GC 存在循环引用问题,导致无法辨别无用对象,而 GC ROOT 方式不存在循环引用的问题引用计数GC ROOT 的实现机理很易理解,面试时大家都能流利应答,那怎么才能脱颖而出呢?思考一个问题:不通过 GC ...
  • 引用计数方式 GC 存在循环引用问题,导致无法辨别无用对象,而 GC ROOT 方式不存在循环引用的问题❞引用计数GC ROOT 的实现机理很易理解,面试时大家都能流利应答,那怎么才能脱颖而出呢?思考一个问题:「不...
  • 引用计数方式 GC 存在循环引用问题,导致无法辨别无用对象,而 GC ROOT 方式不存在循环引用的问题 引用计数GC ROOT 的实现机理很易理解,面试时大家都能流利应答,那怎么才能脱颖而出呢?思考一个问题:不通过 ...
  • 常见的GC回收算法主要包括引用计数算法、标记清除算法、复制算法、标记压缩算法、分代算法以及分区算法。今天来聊聊引用计数算法。1原理顾名思义,此种算法会在每一个对象上记录这个对象被引用的次数,只要有任何一...
  • PHP--GC引用计数

    万次阅读 2018-07-23 16:36:47
     引用计数:  每个php变量存在一个叫"zval"的变量容器中。一个zval变量容器,除了包含变量的类型和值,还包括两个字节的额外信息。 第一个 是"is_ref",是个bool值,用来标识这个变量是否...
  • GC:垃圾回收 GC的作用区域只有堆(包含方法区) JVM在进行GC时,并不是对这三个区域统一回收,大部分的时候,回收都是新生代 新生区 幸存区(from,to),两个区是动态交换的 ...引用计数法(一般不采用)
  • GC_3_引用计数

    千次阅读 2017-04-03 10:28:55
    3 引用计数法   GC是一种,释放怎么都无法被引用的对象的机制。可以让所有对象事先记录下有多少程序引用自己,让各对象知道自己的人气指数,从而让没有人气的对象自己消失,这就是引用计数法(Reference Counting...
  • 引用计数 vs. GC

    2016-08-22 11:37:00
    内存管理问题 内存管理是编程过程中的一个经典问题,早期在 C 语言...引用计数 (ARC,Automatic Reference Counting) GC (Garbage Collection) 管理方法 ARC/GC 因为 Java 的流行,GC 被广泛的认知。GC 简单...
  • python引用计数gc垃圾回收

    千次阅读 2017-03-05 13:46:15
    一,引用计数和垃圾回收介绍: python采用"引用计数"和"垃圾回收"两种机制来管理内存。 引用计数通过记录对象被引用的次数来管理对象。 对对象的引用都会使得引用计数加1,移除对对象的引用,引用计数则会减1, 当...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 4,558
精华内容 1,823
关键字:

gc引用计数