循环引用_for循环外创建引用和for循环里创建引用对象区别 - CSDN
  • 导致iOS对象无法按预期释放的一个无形杀手是——循环引用循环引用可以简单理解为A引用了B,而B又引用了A,双方都同时保持对方的一个引用,导致任何时候引用计数都不为0,始终无法释放。若当前对象是一个...
     ARC已经出来很久了,自动释放内存的确很方便,但是并非绝对安全绝对不会产生内存泄露。导致iOS对象无法按预期释放的一个无形杀手是——循环引用。循环引用可以简单理解为A引用了B,而B又引用了A,双方都同时保持对方的一个引用,导致任何时候引用计数都不为0,始终无法释放。若当前对象是一个ViewController,则在dismiss或者pop之后其dealloc无法被调用,在频繁的push或者present之后内存暴增,然后APP就duang地挂了。下面列举我们变成中比较容易碰到的三种循环引用的情形。

    (1)计时器NSTimer

    一方面,NSTimer经常会被作为某个类的成员变量,而NSTimer初始化时要指定self为target,容易造成循环引用。 另一方面,若timer一直处于validate的状态,则其引用计数将始终大于0。先看一段NSTimer使用的例子(ARC模式):

    #import <Foundation/Foundation.h>

     @interface Friend : NSObject

     - (void)cleanTimer;

     @end

     #import "Friend.h"

     @interface Friend ()

     {

             NSTimer *_timer;

         }

     @end


     @implementation Friend

     - (id)init

     {

             if (self = [super init]) {

                     _timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(handleTimer:)

                                                                                  userInfo:nil repeats:YES];

                 }

             return  self;

         }


     - (void)handleTimer:(id)sender

     {

             NSLog(@"%@ say: Hi!", [self class]);

         }

     - (void)cleanTimer

     {

             [_timer invalidate];

             _timer = nil;

         }

     - (void)dealloc

     {

             [self cleanTimer];

             NSLog(@"[Friend class] is dealloced");

         }

    在类外部初始化一个Friend对象,并延迟5秒后将friend释放(外部运行在非arc环境下)


             Friend *f = [[Friend alloc] init];

             dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5*NSEC_PER_SEC), dispatch_get_main_queue(), ^{

                     [f release];

                 });

    我们所期待的结果是,初始化5秒后,f对象被releasefdealloc方法被调用,在dealloc里面timer失效,对象被析构。但结果却是如此:
     


    2015-03-18 18:00:35.300 WZLCodeLibrary[41422:3390529] Friend say: Hi!
    2015-03-18 18:00:36.299 WZLCodeLibrary[41422:3390529] Friend say: Hi!
    2015-03-18 18:00:37.300 WZLCodeLibrary[41422:3390529] Friend say: Hi!
    2015-03-18 18:00:38.299 WZLCodeLibrary[41422:3390529] Friend say: Hi!
    2015-03-18 18:00:39.299 WZLCodeLibrary[41422:3390529] Friend say: Hi!//运行了5次后没按照预想的停下来
    2015-03-18 18:00:40.299 WZLCodeLibrary[41422:3390529] Friend say: Hi!
    2015-03-18 18:00:41.300 WZLCodeLibrary[41422:3390529] Friend say: Hi!
    2015-03-18 18:00:42.300 WZLCodeLibrary[41422:3390529] Friend say: Hi!
    2015-03-18 18:00:43.299 WZLCodeLibrary[41422:3390529] Friend say: Hi!
    2015-03-18 18:00:44.300 WZLCodeLibrary[41422:3390529] Friend say: Hi!<br>.......根本停不下来.....


     
    这是为什么呢?主要是因为从timer的角度,timer认为调用方(Friend对象)被析构时会进入dealloc,在dealloc可以顺便将timer的计时停掉并且释放内存;但是从Friend的角度,他认为timer不停止计时不析构,那我永远没机会进入dealloc。循环引用,互相等待,子子孙孙无穷尽也。问题的症结在于-(void)cleanTimer函数的调用时机不对,显然不能想当然地放在调用者的dealloc中。一个比较好的解决方法是开放这个函数,让Friend的调用者显式地调用来清理现场。如下:


    Friend *f = [[Friend alloc] init];

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5*NSEC_PER_SEC), dispatch_get_main_queue(), ^{

        [f cleanTimer];

        [f release];

    });


    2block


    blockcopy时都会对block内部用到的对象进行强引用(ARC)或者retainCount1(ARC)。在ARC与非ARC环境下对block使用不当都会引起循环引用问题,一般表现为,某个类将block作为自己的属性变量,然后该类在block的方法体里面又使用了该类本身,简单说就是self.someBlock = ^(Type var){[self dosomething];或者self.otherVar = XXX;或者_otherVar = ...};block的这种循环引用会被编译器捕捉到并及时提醒。举例如下,依旧以Friend类为例子:


    #import "Friend.h"


    @interface Friend ()

    @property (nonatomic) NSArray *arr;

    @end


    @implementation Friend

    - (id)init

    {

        if (self = [super init]) {

            self.arr = @[@111, @222, @333];

            self.block = ^(NSString *name){

                NSLog(@"arr:%@", self.arr);

            };

        }

        return  self;

    }

    我们看到,在block的实现内部又使用了Friend类的arr属性,xcode给出了warning 运行程序之后也证明了Friend对象无法被析构:

    self.arr = @[@111,@222,@333];

    self.block = ^(NSString *name){

        NSLog(@"arr:%@",self.arr);

    }

    网上大部分帖子都表述为"block里面引用了self导致循环引用",但事实真的是如此吗?我表示怀疑,其实这种说法是不严谨的,不一定要显式地出现"self"字眼才会引起循环引用。我们改一下代码,不通过属性self.arr去访问arr变量,而是通过实例变量_arr去访问,如下:
     

    _arr = @[@111,@222,@333];

    self.block = ^(NSString *name){

        NSLog(@"arr:%@",_arr);

    }


    由此我们知道了,即使在你的block代码中没有显式地出现"self",也会出现循环引用!只要你在block里用到了self所拥有的东西!但对于这种情况,目前我不知道该如何排除掉循环引用,因为我们无法通过加__weak声明或者__block声明去禁止blockself进行强引用或者强制增加引用计数。对于self.arr的情况,我们要分两种环境去解决:


    1)ARC环境下:ARC环境下可以通过使用_weak声明一个代替self的新变量代替原先的self,我们可以命名为weakSelf。通过这种方式告诉block,不要在block内部对self进行强制strong引用:(如果要兼容ios4.3,则用__unsafe_unretained代替__weak,不过目前基本不需考虑这么low的版本)


              self.arr = @[@111, @222, @333];

             __weak typeof(self) weakSelf=self;

             self.block = ^(NSString *name){

                     NSLog(@"arr:%@", weakSelf.arr);

                 };

    2)MRC环境下:解决方式与上述基本一致,只不过将__weak关键字换成__block即可,这样的意思是告诉block:小子,不要在内部对self进行retain了!


    3)委托delegate


    在委托问题上出现循环引用问题已经是老生常谈了,本文也不再细讲,规避该问题的杀手锏也是简单到哭,一字诀:声明delegate时请用assign(MRC)或者weak(ARC),千万别手贱玩一下retain或者strong,毕竟这基本逃不掉循环引用了!


    展开全文
  • java之循环引用

    2018-12-13 14:49:26
    在学习java内存模型及垃圾回收时提到了引用计数法无法解决循环引用的问题,心里一直在思考怎么才是循环引用。 netty中的循环引用的例子。 例如:NioServerSocketChannel类中有内部类NioServerSocketChannelConfig。...

    在学习java内存模型及垃圾回收时提到了引用计数法无法解决循环引用的问题,心里一直在思考怎么才是循环引用。
    netty中的循环引用的例子。

    例如:NioServerSocketChannel类中有内部类NioServerSocketChannelConfig。每一个NioServerSocketChannel实例对象都有全局变量NioServerSocketChannelConfig的实例对象,而NioServerSocketChannelConfig实例对象在构造的时候也是需要将NioServerSocketChannel实例对象引用进去。也就是NioServerSocketChannel实例对象和NioServerSocketChannelConfig实例对象相互引用了。也就是循环引用。

    public class NioServerSocketChannel extends AbstractNioMessageChannel
                                 implements io.netty.channel.socket.ServerSocketChannel {
    
        private static final ChannelMetadata METADATA = new ChannelMetadata(false, 16);
        private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider();
    
        private static final InternalLogger logger = InternalLoggerFactory.getInstance(NioServerSocketChannel.class);
    
      
    	//NioServerSocketChannel实例对象的全局变量ServerSocketChannelConfig的实例
        private final ServerSocketChannelConfig config;
    
    public NioServerSocketChannel(ServerSocketChannel channel) {
        super(null, channel, SelectionKey.OP_ACCEPT);
        //在初始化config的时候将自己(NioServerSocketChannel的实例对象)也传入了
        config = new NioServerSocketChannelConfig(this, javaChannel().socket());
    }
    
    private NioServerSocketChannelConfig(NioServerSocketChannel channel, ServerSocket javaSocket) {
        super(channel, javaSocket);
    }
    
    展开全文
  • 垃圾回收是一门编程语言中...在编程语言中,普遍存在着循环引用这样的问题,垃圾回收器是如何处理循环引用呢,常用的垃圾回收有引用计数和引用对象遍历两种实现,它们各自又是如何处理循环引用呢?本文讲以JVM中的GC...

    垃圾回收是一门编程语言中必不可少的一部分,不论是手动释放内存的C和C++,还是自动回收垃圾的Java和C#等语言。对于Java这样的语言,一般的开发者不强求关心对象回收和内存释放,但是理解垃圾回收对开发工作还是大有裨益的。

    在编程语言中,普遍存在着循环引用这样的问题,垃圾回收器是如何处理循环引用呢,常用的垃圾回收有引用计数和引用对象遍历两种实现,它们各自又是如何处理循环引用呢?本文讲以JVM中的GC为例逐一回答这些问题。

    何为循环引用

    如果有两个或者以上的对象,它们彼此引用,就会造成循环引用。如下面的例子

    1
    2
    3
    4
    5
    6
    7
    8
    
    class Node {
      Node next;
    }
    
    Node a = new Node();
    Node b = new Node();
    a.next = b;
    b.next = a;
    

    代码中,a对象引用了b对象,b对象也引用了a对象,这种情况下a对象和b对象就形成了循环引用。

    引用计数GC处理

    什么是引用计数

    引用计数是一种垃圾回收的形式,每一个对象都会有一个计数来记录有多少指向它的引用。其引用计数会变换如下面的场景

    • 当对象增加一个引用,比如赋值给变量,属性或者传入一个方法,引用计数执行加1运算。
    • 当对象减少一个引用,比如变量离开作用域,属性被赋值为另一个对象引用,属性所在的对象被回收或者之前传入参数的方法返回,引用计数执行减1操作。
    • 当引用计数变为0,代表该对象不被引用,可以标记成垃圾进行回收。

    如何处理

    实际上单纯的基于引用计数实现的计数器无法处理循环引用带来的问题。

    CPython的垃圾回收就是采用引用计数,采用引用计数的主垃圾回收器会清理垃圾,对于那些因为循环引用无法清理的对象,CPython会不时启动一个辅助的基于引用遍历的垃圾回收器来清理它们。

    引用遍历GC处理

    什么是引用对象遍历

    垃圾回收器从被称为GC Roots的点开始遍历遍历对象,凡是可以达到的点都会标记为存活,堆中不可到达的对象都会标记成垃圾,然后被清理掉。 GC Roots有哪些

    • 类,由系统类加载器加载的类。这些类从不会被卸载,它们可以通过静态属性的方式持有对象的引用。注意,一般情况下由自定义的类加载器加载的类不能成为GC Roots
    • 线程,存活的线程
    • Java方法中的局部变量或者参数
    • JNI方法栈中的局部变量或者参数
    • JNI全局引用
    • 用做同步监控的对象
    • 被JVM持有的对象,这些对象由于特殊的目的不被GC回收。这些对象可能是系统的类加载器,一些重要的异常处理类,一些为处理异常预留的对象,以及一些正在执行类加载的自定义的类加载器。但是具体有哪些前面提到的对象依赖于具体的JVM实现。

    如何处理

    基于引用对象遍历的垃圾回收器可以处理循环引用,只要是涉及到的对象不能从GC Roots强引用可到达,垃圾回收器都会进行清理来释放内存。

    总结

    基于引用计数的垃圾回收器无法处理循环引用导致的内存泄露问题,但是其在主流的JVM中很少,几乎所有的JVM都是采用引用对象遍历的方法,垃圾回收器都会处理循环引用潜在的问题。

    展开全文
  • 循环引用:指的是多个对象相互引用时,使得引用形成一个环形,导致外部无法真正是否掉这块环形内存。其实有点类似死锁。 比如如我有一个people类,在有一个car,people有一个car的属性,car类中又people的属性,此时...

    循环引用:指的是多个对象相互引用时,使得引用形成一个环形,导致外部无法真正是否掉这块环形内存。其实有点类似死锁。
    比如如我有一个people类,在有一个car,people有一个car的属性,car类中又people的属性,此时产生循环引用问题.

    1.循环引用导致内存永远不被清理例子

    首先来看一个循环引用导致内存泄露的例子.

    #include <iostream>
    #include <string>
    #include <vector>
    #include <memory>
    using namespace std;
    
    class Person {
      public:
        string name;
        Person* mother;
        Person* father;
        vector<Person*> kids;
    
        Person (const string& n,
                Person* m = nullptr,
                Person* f = nullptr)
         : name(n), mother(m), father(f) {
        }
    
        ~Person() {
          cout << "delete " << name << endl;
        }
    };
    
    Person* initFamily (const string& name)
    {
        Person* mom(new Person(name+"'s mom"));
        Person* dad(new Person(name+"'s dad"));
        Person* kid(new Person(name,mom,dad));
        mom->kids.push_back(kid);
        dad->kids.push_back(kid);
        return kid;
    }
    
    int main()
    {
        Person* p = initFamily("nico");
        cout << "nico's family exists" << endl;
        return 0;
    }
    

    此程序运行完后输出
    nico’s family exists
    由于没有手动调用析构函数,会出现内存泄露问题.
    使用valgrind检测

    valgrind --tool=memcheck --leak-check=full ./leaktest

    definitely lost: 128 bytes in 2 blocks
    证明确实有内存泄露.

    3内存泄露原因分析

    循环引用如下图所示,
    这里写图片描述
    如图,mon和dad引用了kid,kid引用了mon和dad.
    当释放最后一个引用该家庭的指针时,家庭的每个成员至少被一个 pointer 指向.造成内存泄露.

    3解决方法1:使用weak_ptr

    循环引用解决原则是:
    父引子强引用,子引父弱引用。就是避免两个强指针相互引用.
    下面代码使用c++11中的智能指针weak_ptr解决循环引用.

    #include <iostream>
    #include <string>
    #include <vector>
    #include <memory>
    using namespace std;
    
    class Person {
      public:
        string name;
        shared_ptr<Person> mother;
        shared_ptr<Person> father;
        vector<weak_ptr<Person>> kids;  // weak pointer !!!
    
        Person (const string& n,
                shared_ptr<Person> m = nullptr,
                shared_ptr<Person> f = nullptr)
         : name(n), mother(m), father(f) {
        }
    
        ~Person() {
          cout << "delete " << name << endl;
        }
    };
    
    shared_ptr<Person> initFamily (const string& name)
    {
        shared_ptr<Person> mom(new Person(name+"'s mom"));
        shared_ptr<Person> dad(new Person(name+"'s dad"));
        shared_ptr<Person> kid(new Person(name,mom,dad));
        weak_ptr<Person> wkid(kid);
        mom->kids.push_back(wkid);
        dad->kids.push_back(wkid);
        return kid;
    }
    
    int main()
    {
        shared_ptr<Person> p = initFamily("nico");
    
        cout << "nico's family exists" << endl;
        cout << "- nico is shared " << p.use_count() << " times" << endl;
        cout << "- name of 1st kid of nico's mom: "
             << p->mother->kids[0].lock()->name << endl;
        return 0;
    }

    使用weakptr后关系如图.
    这里写图片描述

    注意,程序的中p.use_count()输出为1.
    c++ primer中如下描述weak_ptr:
    将一个weak_ptr绑定到一个share_ptr 不会改变shared_ptr的引用计数.一旦最后一个指向对象的shared_ptr被销毁,对象被释放.即使weak_ptr指向对象,对象还是被释放.
    以上程序initFamily时将一个weak_ptr wkid 绑定到share_ptr kid,不会改变shared_ptr kid的引用计数.当程序结束时,shared_ptr mom,dad,kid被销毁,对象被释放,即使mom和dad的weak_ptr指向对象,对象还是被释放.

    4.解决方法2:由程序逻辑手动释放内存

    在这个内存泄露的程序中,有三个对象,mom,dad,kid,他们相互引用.因此,依次手动释放他们分配的内存就能够避免内存泄露.
    解决方法如下:

    #include <iostream>
    #include <string>
    #include <vector>
    #include <memory>
    using namespace std;
    
    class Person {
      public:
        string name;
        Person* mother;
        Person* father;
        vector<Person*> kids;
    
        Person (const string& n,
                Person* m = nullptr,
                Person* f = nullptr)
         : name(n), mother(m), father(f) {
        }
    
        ~Person() {
          cout << "delete " << name << endl;
        }
    };
    
    Person* initFamily (const string& name)
    {
        Person* mom(new Person(name+"'s mom"));
        Person* dad(new Person(name+"'s dad"));
        Person* kid(new Person(name,mom,dad));
        mom->kids.push_back(kid);
        dad->kids.push_back(kid);
        return kid;
    }
    
    int main()
    {
        Person* p = initFamily("nico");
        cout << "nico's family exists" << endl;
        delete p->mother;
        delete p->father;
        delete p;
        return 0;
    }

    参考:
    The C++ Standard Library - A Tutorial and Reference, 2nd Edition

    展开全文
  • 关于引用计数法,我们可以先看一段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...
  • 导致iOS对象无法按预期释放的一个无形杀手是——循环引用循环引用可以简单理解为A引用了B,而B又引用了A,双方都同时保持对方的一个引用,导致任何时候引用计数都不为0,始终无法释放。若当前对象是一个...
  • 循环引用

    2020-07-31 17:39:47
    总结关于循环引用,如何回答面试: 首先spring在单例的情况下是默认支持循环引用的(当然原形也有办法,今天先不讨论);在不做任何配置的情况下,两个bean相互依赖是能初始化成功的;spring源码中在创建bean的时候先...
  • 循环引用和解决方案

    2017-07-26 17:29:30
    如何处理循环引用问题? 什么是循环引用?顾名思义,两个对象之间形成环路。对于C++中两个对象来讲,使用智能指针解决不了这个问题。因为其引用计数不为0。循环引用解决办法循环引用涉及的问题是垃圾回收(Garbage ...
  • [C++11] 循环引用

    2018-09-30 13:01:52
    的使用同时也引出了另一个问题:循环引用。 例子 让我们先来看一段示例代码。 #include &lt;iostream&gt; #include &lt;vector&gt; #include &lt;memory&gt; using namespace std; class ...
  • C++最新标准C++11中已将基于引用计数的智能指针share_prt收入囊中,智能指针的使用门槛越来越低,不需要使用boost库,我们也能轻松享受智能指针给我们带来的方便。 智能指针,正如它的名字一样,似乎是个近乎完美的...
  • iOS开发-循环引用

    2019-08-09 00:37:19
    一、循环引用的产生 -- :表示弱引用。 -> :表示强引用。 循环引用可以简单理解为对象A引用了对象B,而对象B又引用了对象A:A -> B -> A,此时双方都同时保持对方的一个引用,导致任何时候双方的引用...
  • 内存泄漏及浪费都是每个程序员非常忌讳的事情,但是稍有不慎就会造成不可弥补的损失,这里就这能指针sheard_ptr造成的循环引用及造成循环引用的原因我给大家给出了详细的图解及举例说明,同时也提供了避免这种循环...
  • 组件之间循环引用

    2018-11-03 16:06:00
    是为了便于区分两个组件,其实这两个组件互为 祖先和后代的关系,因为他们互相循环引用,不是简单的 父子关系 &lt;div id="div"&gt; &lt;!--用prop 获取实例data数据--&gt; &...
  • 导致iOS对象无法按预期释放的一个无形杀手是——循环引用循环引用可以简单理解为A引用了B,而B又引用了A,双方都同时保持对方的一个引用,导致任何时候引用计数都不为0,始终无法释放。若当前对象是一个...
  • c++中的头文件循环引用 问题 在项目文件变多时,由于组织不当,很容易出现头文件的循环引用 有时候虽然没有出现循环引用,但是头文件多处被include,导致编译速度变慢 解决办法 适当的使用前置声明 什么...
  • 经常在一些面试题里面或者其他规范里面看到,block里面不能用self,否则会产生循环引用,但是为什么不能用self,循环引用是什么,如何产生的循环引用,这些总是一知半解。带着问题,查询了一些资料了解了一部分产生...
  • 引用符号 引用 描述 "$ref":".." 上一级 "$ref":"@" 当前对象,也就是自引用 "$ref"... 1、什么是Json的重复引用和循环引用? 重复引
  • 但这个方法有个问题,就是无法解决循环引用的问题。 循环引用就是对象A引用了对象B,对象B引用了对象A,构成了一个引用环。彼此都没发挥什么作用,但却不会被GC。为了解决这个问题,就有了可达性分析回收算法。 ...
  • 最近项目使用django重构,由于使用了代理类继承models中的类,为了可以使用代理类中的方法,models中的类的外键又是指向代理类的,毫无疑问这样发生了循环引用。  方案一(临时解决方案---不推荐):在代理类以及...
  • 在工作时遇到的Excel表格整理,从不同的Excel列统计数据 粘到一个表中时,会发生【循环引用】,Excel保存关闭后再打开,出现提示信息:循环引用。现象:关闭一个Excel还有另一个Excel。 解决:2步 1、要取消...
1 2 3 4 5 ... 20
收藏数 556,593
精华内容 222,637
关键字:

循环引用