-
2021-04-11 15:03:28
目录
3.getSingleton(String beanName, ObjectFactory singletonFactory)方法
4.createBean(beanName, mbd, args)方法
5. addSingletonFactory(beanName, singletonFactory) 方法
6.addSingleton(beanName, singletonObject)方法
一、前言
循环引用,简单来说是指A类中依赖B类,B类中又依赖A类,当然这个引用的链也可能更长(A->B->C->....->A);
Spring中对于单例模式的循环引用主要是两种,一种是构造函数的循环引用,一种是set注入的循环引用
构造函数的循环引用无法解决(后边会讲原因),这里主要讲set注入的循环引用,假设目前我们的引用链是A类依赖B类,B类也依赖A类(A->B->A)
二、源码分析
当spring解析注解或配置文件之后,所有的类将会以BeanDefinition的形式保存在容器中,然后对这些BeanDefinition实例化。
实例化用到的方法主要是AbstractBeanFactory. getBean(String name);
1.getBean(String name)方法
调用链 AbstractBeanFactory.getBean(String name); ->AbstractBeanFactory. doGetBean(final String name, @Nullable final Class<T> requiredType, @Nullable final Object[] args, boolean typeCheckOnly) 代码如下: protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException { //传入的name可能是alias,也可能有&前缀(工厂类) 得转化一下 final String beanName = transformedBeanName(name); Object bean; //首先从缓存中获取 这里讲的缓存主要是 //singletonObjects, earlySingletonObjects 重点 Object sharedInstance = getSingleton(beanName); if (sharedInstance != null && args == null) { ....//省略部分代码 //如果缓存中存在 因为可能得到的事FactoryBean //而我们需要的是FactoryBean.getObject创建的对象 在此作处理 bean = getObjectForBeanInstance(sharedInstance, name, beanName, null); } else { //如果缓存中没有 ......//省略部分代码 try { //我们的类可能继承了父类 将父类的属性合并一下 final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); checkMergedBeanDefinition(mbd, beanName, args); //xml配置文件中使用了depend-on属性或者java文件中使用了@DependsOn 依赖的类先实例化 String[] dependsOn = mbd.getDependsOn(); if (dependsOn != null) { for (String dep : dependsOn) { if (isDependent(beanName, dep)) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'"); } registerDependentBean(dep, beanName); try { getBean(dep); } catch (NoSuchBeanDefinitionException ex) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "'" + beanName + "' depends on missing bean '" + dep + "'", ex); } } } //如果这个BeanDefinition是单例 if (mbd.isSingleton()) { //创建对象 主要是通过ObjectFactory创建 //注意:这里使用lambda表达式 创建了一个匿名内部类工厂 sharedInstance = getSingleton(beanName, () -> { try { return createBean(beanName, mbd, args); } catch (BeansException ex) { destroySingleton(beanName); throw ex; } }); //如果缓存中存在 因为可能得到的事FactoryBean //而我们需要的事FactoryBean.getObject创建的对象 在此作处理 bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd); } ......//省略部分代码 } ....//省略部分代码 return (T) bean; }
2.getSingleton(beanName)方法
首先先看看getSingleton(beanName)从缓存中获取做了些啥。
调用链 DefaultSingletonBeanRegistry.getSingleton(String beanName) //return getSingleton(beanName, true); //allowEarlyReference是true ->DefaultSingletonBeanRegistry.getSingleton(String beanName, boolean allowEarlyReference) 代码如下: protected Object getSingleton(String beanName, boolean allowEarlyReference) { //从singletonObjects中获取 这是一个Map<String, Object> Object singletonObject = this.singletonObjects.get(beanName); //singletonsCurrentlyInCreation是一个Set<String> 实例化之后会BeanName加入到集合里 //判断singletonsCurrentlyInCreation是否包含beanName,如果包含beanName 表示该beanDefinition已经实例化 但是 //还没有给属性赋值 (后边会提及) if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { synchronized (this.singletonObjects) { //从 Map<String, Object> earlySingletonObjects获取 singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { //如果earlySingletonObjects中没有 则获取ObjectFactory得到对象的引用 //至于ObjectFactory什么时候放入singletonFactories中 后边会提及 ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { //earlySingletonObjects 和 生成这个对象的ObjectFactory是互斥的 //通过ObjecFactory生成后 就放到earlySingleObjects中,以后无需 //ObjectFactory再创建了 singletonObject = singletonFactory.getObject(); this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } return singletonObject; }
对于之前没有实例化过得beanDefinition,这里肯定返回null,如果缓存中没有,那我们需要自己实例化对象。
3.getSingleton(String beanName, ObjectFactory<?> singletonFactory)方法
接下来我们看DefaultSingleBeanRegistry.getSingleton(String beanName, ObjectFactory<?> singletonFactory)
如下: public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) { Assert.notNull(beanName, "Bean name must not be null"); synchronized (this.singletonObjects) { ......//省略部分代码 //将beanName加入到singletonsCurrentlyInCreation中 beforeSingletonCreation(beanName); ......//省略部分代码 try { singletonObject = singletonFactory.getObject(); newSingleton = true; } catch (IllegalStateException ex) { .... } catch (BeanCreationException ex) { .... } finally { if (recordSuppressedExceptions) { this.suppressedExceptions = null; } afterSingletonCreation(beanName); } if (newSingleton) { addSingleton(beanName, singletonObject); } } return singletonObject; } }
这里主要是将beanName加入到singletonsCurrentlyInCreation集合中(前面从缓存中取对象时用到过),然后调用ObjectFactory.getObject方法 而通过doGetBean方法里匿名内部类可知,就是调用createBean(beanName, mbd, args)方法。
4.createBean(beanName, mbd, args)方法
代码如下: protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException { ......//省略部分代码 try { Object beanInstance = doCreateBean(beanName, mbdToUse, args); if (logger.isTraceEnabled()) { logger.trace("Finished creating instance of bean '" + beanName + "'"); } return beanInstance; } catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) { ... } catch (Throwable ex) { ... } 调用链: ->doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) //实例化对象的关键 protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException { // Instantiate the bean. BeanWrapper instanceWrapper = null; if (mbd.isSingleton()) { instanceWrapper = this.factoryBeanInstanceCache.remove(beanName); } //调用构造函数实例化对象 //到此对象已经被实例化了,只是依赖的属性还没有被赋值或实例化 //比如 A类中有个属性B,需要进行注入 此时B是没有注入的 if (instanceWrapper == null) { instanceWrapper = createBeanInstance(beanName, mbd, args); } final Object bean = instanceWrapper.getWrappedInstance(); Class<?> beanType = instanceWrapper.getWrappedClass(); if (beanType != NullBean.class) { mbd.resolvedTargetType = beanType; } ......//省略部分代码 boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { //如果是单例&&允许循环依赖&&对象正在构建中(已经实例化,但是属性没有赋值) //isSingletonCurrentlyInCreation就是判断singletonsCurrentlyInCreation //中有没有beanName,在前面我们已经将beanName加入到该集合中了 if (logger.isTraceEnabled()) { logger.trace("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references"); } //将beanName和ObjectFactory放入SingleFactories中 //ObjectFactory 返回的事这里对象的引用(已经实例化,但是它的属性还没有赋值) addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } // Initialize the bean instance. Object exposedObject = bean; try { //给对象的属性赋值 populateBean(beanName, mbd, instanceWrapper); //调用诸如init-method等初始化方法 exposedObject = initializeBean(beanName, exposedObject, mbd); } catch (Throwable ex) { ... } ......//省略部分代码 return exposedObject; }
5. addSingletonFactory(beanName, singletonFactory) 方法
这里我们注重看addSingletonFactory方法
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { Assert.notNull(singletonFactory, "Singleton factory must not be null"); synchronized (this.singletonObjects) { if (!this.singletonObjects.containsKey(beanName)) { this.singletonFactories.put(beanName, singletonFactory); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } } }
初次实例化singletonObjects肯定不包含该beanName,这里我们将bean,ObjectFactory加入到了singleFactories,以便下次实例化时从缓存获取的时候 就可以通过这个ObjectFactory得到对象的引用了(虽然只是实例化了,属性还没有赋值,但当后边的 populateBean(beanName, mbd, instanceWrapper);执行完之后,属性就赋值完成了)
6.addSingleton(beanName, singletonObject)方法
最后我们再看一下Object getSingleton(String beanName, ObjectFactory<?> singletonFactory)方法中的addSingleton(beanName, singletonObject);
protected void addSingleton(String beanName, Object singletonObject) { synchronized (this.singletonObjects) { this.singletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } }
当对象实例化流程走完之后,将beanName,singleObject放入singletonObjects中,以便其他对象依赖时从缓存中获取的时候进行返回
三、总结
spring中解决循环依赖 假设A类依赖B类,B类依赖A类(A->B->A)
那么当A实例化时会从缓存中获取
首先从singleObjects中获取,singleObjcts存放的对象是已经实例化,并且属性也赋值了
如果singleObjects中没有 则从earlySingletonObjects中获取,earlySingleObjects存放的是对象已经实例化,但是属性还没有赋值
如果earlySingleObjects中没有 则使用ObjectFactory获取对象的引用,放入earlySingleObjects中
当然如果A是第一次实例化,从缓存中返回null
这时需要我们自己实例化,首先调用构造函数实例化,然后在属性赋值前将beanName,和返回对象引用的ObjectFactory加入到singleFactories中(这也是构造函数依赖不能解决的原因,因为ObjectFactory加入到singleFactories中是在构造函数实例化之后)
最后进行属性赋值,
因为A依赖B,我们需要实例化B,对B做同样的处理,当B又依赖A时因为我们已经将获取A引用的ObjectFactory放入了singleFactories中,所以这里我们能获取到A的引用
最终将B的beanName,singleObject加入到缓存singleObjects中,向上递归,将A的beanName,siingleObject加入到singleObjects中。
附图:
更多相关内容 -
关于c++ 智能指针及 循环引用的问题
2020-12-26 04:46:03c++智能指针介绍 由于 C++ 语言没有自动内存回收机制,程序员每次 new 出来的内存都要... RAII 的主要原则是为所有堆分配资源提供所有权,例如动态分配内存或系统对象句柄、析构函数包含要删除或释放资源的代码的堆栈分 -
javascript深拷贝、浅拷贝和循环引用深入理解
2020-12-11 18:03:19引用类型值指的是那些保存在堆内存中的对象,所以引用类型的值保存的是一个指针,这个指针指向存储在堆中的一个对象。除了上面的 6 种基本数据类型外,剩下的就是引用类型了,统称为 Object 类型。细分的话,有:... -
block的循环引用
2021-12-03 19:33:05相互循环引用:如果当前block对当前对象的某一成员变量进行捕获的话,可能会对它产生强引用。根据block的变量捕获机制,如果block被拷贝到堆上,且捕获的是对象类型的auto变量,则会连同其所有权修饰符一起捕获,...前言
前面我们了解了block的捕获变量机制和 block的copy实现,本文通过底层代码说一下block的循环引用
为什么 block 会产生循环引用
Q: 为什么 block 会产生循环引用?
相互循环引用:
如果当前block访问当前对象的某一成员变量,会讲当前对象进行捕获,可能会对它产生强引用。根据block的变量捕获机制,如果block被拷贝到堆上,且捕获的是对象类型的auto变量,则会连同其所有权修饰符一起捕获,所以如果对象是__strong修饰,则block会对它产生强引用(如果block在栈上就不会强引用)。而当前block可能又由于当前对象对其有一个强引用,就产生了相互循环引用的问题
多方循环引用:
如果使用__block的话,在ARC下可能会产生循环引用,由于__block修饰符会将变量包装成一个对象,如果block被拷贝到堆上,则会直接对__block变量产生强引用,而__block如果修饰的是对象的话,会根据对象的所有权修饰符做出相应的操作,形成强引用或者弱引用,如果对象是__strong修饰,则__block变量对它产生强引用,如果这时候该对象是对block持有强引用的话,就产生了三方循环引用的问题。相互循环引用
多方循环引用
为什么我们要处理循环引用?
循环引用会导致实例对象不能释放 也就是实例对象所占用的内存不能及时被系统回收,会造成内存泄漏。
内存泄漏又是什么?危害是什么?
已经动态分配的堆内存由于某种原因程序未释放或无法释放造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果常见的内存泄漏有哪些
1、对象之间的循环引用问题
2、block的循环引用
3、delegate 的循环引用
4、通知的循环引用
5、NSTimer和target的循环引用
6、WKWebView 和self 之间的循环引用
了解更多看我这篇文章所以我们在ARC环境下 特别容易block循环引用。
ARC
定义一个 Person类 代码如下
typedef void(^MyBlock)(void); @interface Person : NSObject { @public NSString *name; } @property (nonatomic, assign) int age; @property (nonatomic, strong) MyBlock myblock; @property (nonatomic, strong) void(^block2)(void); @end @implementation Person - (void)dealloc { NSLog(@"%s", __func__); } @end
VC类中调用
- (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = [UIColor whiteColor]; Person *person = [[Person alloc] init]; person.age = 30; person.myblock = ^{ NSLog(@"-------30"); }; NSLog(@"%@",[person.myblock class]); NSLog(@"%ld",CFGetRetainCount((CFTypeRef)(person))); } - (void)dealloc { NSLog(@"%s",__func__); }
输出
__NSGlobalBlock__ 1 -[Person dealloc] -[VC dealloc]
分析
Person实例对象销毁是正常
VC实例对象销毁是正常看一个不正常的情况 代码如下
- (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = [UIColor whiteColor]; Person *person = [[Person alloc] init]; person.age = 30; person.myblock = ^{ NSLog(@"-------%d", person.age); }; NSLog(@"%ld",CFGetRetainCount((CFTypeRef)(person))); } - (void)dealloc { NSLog(@"%s",__func__); }
输出
__NSMallocBlock__ 3 -[VC dealloc]
发生循环引用导致Person实例对象没有被释放,会引起内存泄漏
Clang
Person.cpp重要代码注解
// 所有对象的结构体的类型都是objc_object typedef struct objc_object Person; //objc_object是只包含一个成员变量isa的结构体 struct objc_object { Class _Nonnull isa __attribute__((deprecated)); }; struct NSObject_IMPL { __unsafe_unretained Class isa; }; // 对象的本质就是结构体 Person实例对象 被转化为Person_IMPL结构体 struct Person_IMPL { // 对象的继承就是父类作为成员变量 // isa指向NSobject struct NSObject_IMPL NSObject_IVARS; // _name 没有声明成属性 没有帮我们生成 set get方法 NSString *__strong _name; int _age; __strong MyBlock _myblock; void (*_block2)(); }; // @property (nonatomic, assign) int age; // @property (nonatomic, strong) MyBlock myblock; // @property (nonatomic, strong) void(^block2)(void); /* @end */ // @implementation Person // 根据属性修饰符 生成age属性get set方法 static int _I_Person_age(Person * self, SEL _cmd) { return (*(int *)((char *)self + OBJC_IVAR_$_Person$_age)); } static void _I_Person_setAge_(Person * self, SEL _cmd, int age) { (*(int *)((char *)self + OBJC_IVAR_$_Person$_age)) = age; } // 生成 myblock属性 get set方法 static void(* _I_Person_myblock(Person * self, SEL _cmd) )(){ return (*(__strong MyBlock *)((char *)self + OBJC_IVAR_$_Person$_myblock)); } static void _I_Person_setMyblock_(Person * self, SEL _cmd, MyBlock myblock) { (*(__strong MyBlock *)((char *)self + OBJC_IVAR_$_Person$_myblock)) = myblock; } // 生成 block2属性 get set方法 static void(* _I_Person_block2(Person * self, SEL _cmd) )(){ return (*(void (**)())((char *)self + OBJC_IVAR_$_Person$_block2)); } static void _I_Person_setBlock2_(Person * self, SEL _cmd, void (*block2)()) { (*(void (**)())((char *)self + OBJC_IVAR_$_Person$_block2)) = block2; } static void _I_Person_dealloc(Person * self, SEL _cmd) { NSLog((NSString *)&__NSConstantStringImpl__var_folders_zm_558cwfjs099fbm2r8kxg8wt00000gt_T_VC_60be72_mi_0, __func__); } // @end
vc.cpp
// VC实例对象 struct VC_IMPL { struct UIViewController_IMPL UIViewController_IVARS; }; struct __VC__viewDidLoad_block_impl_0 { struct __block_impl impl; struct __VC__viewDidLoad_block_desc_0* Desc; // person指针变量 指向Person实例对象 __strong 表示强引用Person实例对象 Person *__strong person; __VC__viewDidLoad_block_impl_0(void *fp, struct __VC__viewDidLoad_block_desc_0 *desc, Person *__strong _person, int flags=0) : person(_person) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };
分析
-
oc对象编译成C++后的本质就是一个结构体Person_IMPL,该结构体的第一个成员变量就是isa指针,指向类对象本身,@property修饰的属性,系统会自动生成一个结构体成员变量,还为之生成getter和setter方法 这些可以看我之前的文章
-
person实例对象结构体Person_IMPL中含有_MyBlock变量,通过setter方法_I_Person_myblock将_MyBlock对象(等号右边)赋值给_MyBlock变量,因此_MyBlock指向_MyBlock对象(强引用)
-
在__VC__viewDidLoad_block_impl_0结构体中,我们看到Person *__strong person,所以,block对象本身对Person实例对象也是强引用
-
block对象结构体__main_block_impl_0通过其内部成员指针变量Person *__strong per持有Person实例对象(强引用),而Person实例对象结构体Person_IMPL通过其内部成员指针变量_MyBlock持有block对象(强引用)因此二者构成循环引用。
图示,我们仔细扣一下这里面的细节
对象持有block的过程
// 实现block person.myblock = ^{ NSLog(@"30"); }; // 根据上面的实现 生成block结构体 __VC__viewDidLoad_block_impl_0 { ..... } // 判断结构体地址 是堆block还是栈block还是全局block // 编译器转化为objc_msgSend 把判断的结果 也就是结构体地址扔给 setMyblock 方法 ((void (*)(id, SEL, MyBlock))(void *)objc_msgSend)((id)person, sel_registerName("setMyblock:"), ((void (*)())&__VC__viewDidLoad_block_impl_0((void *)__VC__viewDidLoad_block_func_0, &__VC__viewDidLoad_block_desc_0_DATA))); // 参数A 就是 结构体地址 参数A: (void (*)())&__VC__viewDidLoad_block_impl_0((void *)__VC__viewDidLoad_block_func_0, &__VC__viewDidLoad_block_desc_0_DATA)) // setMyblock 也就是Myblock的set方法 被上面的objc_msgSend 调用 [person setMyblock: 参数A]; // 将参数A传递进来 赋给OBJC_IVAR_$_Person$_myblock成员变量 static void _I_Person_setMyblock_(Person * self, SEL _cmd, MyBlock myblock) { (*(__strong MyBlock *)((char *)self + OBJC_IVAR_$_Person$_myblock)) = myblock; } person对象通过block成员变量就持有block
block 持有person对象的过程
Person *person = [[Person alloc] init]; // 其实就是指针变量person 强引用[Person alloc]对象 Person *__strong person = [[Person alloc] init]; // 思考下这里 弄成 __weak 会怎样 person.age = 30; person.myblock = ^{ NSLog(@"%@", person.age); }; struct __VC__viewDidLoad_block_impl_0 { struct __block_impl impl; struct __VC__viewDidLoad_block_desc_0* Desc; // block捕获机制 根据变量强弱 内部保持一致 Person *__strong person; __VC__viewDidLoad_block_impl_0(void *fp, struct __VC__viewDidLoad_block_desc_0 *desc, Person *__strong _person, int flags=0) : person(_person) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };
解决思路
打破循环引用,只需要将其中一个强引用变成弱引用即可,那么要改变哪一个弱引用呢?
block从堆上被移除时候 会调用_Block_object_dispose 函数,该函数会自动释放引用的auto变量 也就是说
Person实例对象内部拥有block属性,当该实例对象销毁时,其block属性也会随之销毁,所以我们只需要将block对象中的Person类型指针变成弱指针即可
解决方法ARC
使用__weak
使用__unsafe_unretained
使用__block解决(必须要调用block)MRC
使用__unsafe_unretained
使用__block解决ARC环境
例子1 __weak
使用 weak 修饰
Person *person = [[Person alloc] init]; person.age = 30; __weak Person *weakPer = person; person.myblock = ^{ NSLog(@"%d", weakPer.age); }; NSLog(@"%@",[person.myblock class]);
输出
__NSMallocBlock__ -[Person dealloc] -[VC dealloc]
clang
struct __VC__viewDidLoad_block_impl_0 { struct __block_impl impl; struct __VC__viewDidLoad_block_desc_0* Desc; Person *__weak weakPer; __VC__viewDidLoad_block_impl_0(void *fp, struct __VC__viewDidLoad_block_desc_0 *desc, Person *__weak _weakPer, int flags=0) : weakPer(_weakPer) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };
分析
block对象中,Person指针变量类型变成了__weak类型 Person 实例对象释放正常 VC释放正常
还有一种写法
Person *person = [[Person alloc] init]; person.age = 30; //__weak Person *weakPer = person; __weak typeof(person) weakPer = person; person.myblock = ^{ NSLog(@"%d", weakPer.age); }; person.myblock(); NSLog(@"%@",[person.myblock class]);
例子2 __unsafe_unretained
使用 __unsafe_unretained
Person *person = [[Person alloc] init]; person.age = 30; __unsafe_unretained Person *weakPer = person; person.myblock = ^{ NSLog(@"%d", weakPer.age); }; person.myblock(); NSLog(@"%@",[person.myblock class]);
输出
30 __NSMallocBlock__ -[Person dealloc] -[VC dealloc]
注:__weak修饰和__unsafe_unretained的区别 看我这篇文章
例子3 __block
使用 __block
__block Person *person = [[Person alloc] init]; person.age = 30; person.myblock = ^{ NSLog(@"%d", person.age); person = nil; }; NSLog(@"%@",[person.myblock class]); person.myblock();
输出
__NSMallocBlock__ 30 -[Person dealloc] -[VC dealloc]
clang
struct __Block_byref_person_0 { void *__isa; __Block_byref_person_0 *__forwarding; int __flags; int __size; void (*__Block_byref_id_object_copy)(void*, void*); void (*__Block_byref_id_object_dispose)(void*); Person *__strong person; }; struct __VC__viewDidLoad_block_impl_0 { struct __block_impl impl; struct __VC__viewDidLoad_block_desc_0* Desc; __Block_byref_person_0 *person; // by ref 这默认是强持有 __VC__viewDidLoad_block_impl_0(void *fp, struct __VC__viewDidLoad_block_desc_0 *desc, __Block_byref_person_0 *_person, int flags=0) : person(_person->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };
分析
-
block对象结构体 __VC__viewDidLoad_block_impl_0
-
__block变量对象结构体 __Block_byref_person_0
-
block对象持有__block对象 __block对象又持有Person实例对象,Person实例对象又持有block对象 构成3角循环
- 通过block回调 ,Person实例对象指针被置为nil,而该指针本质是__block对象中的Person *__strong per指针,因此该指针不可能再指向Person实例对象了,所以,第2条持有就断开了,打破了三角循环
- 需要加 __block 需要调用block 还需要把指针制为nil 一旦忘记将指针置为nil,就会造成内存泄露,所以改方法比较麻烦 推荐使用 weak
MRC
该环境下不支持__weak修饰
例子4 __unsafe_unretained
使用 __unsafe_unretained
- (void)test { Person *person = [[Person alloc] init]; person.age = 30; NSLog(@"block前%ld",person.retainCount); __unsafe_unretained Person *uup = person; NSLog(@"__unsafe_unretainedh后%ld",person.retainCount); person.myblock = ^{ NSLog(@"%d", uup.age); }; NSLog(@"block后%ld",person.retainCount); NSLog(@"%@",[person.myblock class]); person.myblock(); [person release]; person = nil; }
输出
block前1 __unsafe_unretainedh后1 block后1 __NSMallocBlock__ 30 -[Person dealloc] -[VC dealloc]
执行 copy
person.myblock = [^{ NSLog(@"%d", uup.age); } copy];
输出
block前1 __unsafe_unretainedh后1 block后1 __NSMallocBlock__ 30 -[Person dealloc] -[VC dealloc]
分析
- 创建并持有1次 block copy到堆区 对象retainCount+1 变成2 对该对象发送release消息时,其retainCount自动减1,所以当程序结束时,Person实例对象retainCount为1,其内存并不会被系统回收从而导致内存泄露。
- 那么加上 __unsafe_unretained修饰后,无论后面有多少次retain或者copy操作,Person实例对象的retainCount始终为1 ,对该对象发送release消息,其retainCount值变为0,此时内存被回收,而不会导致内存泄露的问题
例子5 __block
__block修饰
- (void)test { Person *person = [[Person alloc] init]; person.age = 30; NSLog(@"block前%ld",person.retainCount); __block Person *bp = person; NSLog(@"__block后%ld",person.retainCount); person.myblock = [^{ NSLog(@"%d", bp.age); } copy]; NSLog(@"block后%ld",person.retainCount); NSLog(@"%@",[person.myblock class]); person.myblock(); [person release]; person = nil; }
输出
block前1 __block后1 block后1 __NSMallocBlock__ 30 -[Person dealloc]
分析
MRC下,__block修饰对象类型的auto对象类型,系统生成的__block对象并不会根据test()方法中的person指针是强指针类型而对Person实例对象[[Person alloc进行retain操作
所以此时,__block的作用相当于__unsafe_unretained的作用,原理一样ARC
例子6 __strong
ARC环境下,弱指针不能通过"->"形式来访问对象的成员变量
weakPer很可能为为空(即有可能提前被释放了),所以必须使用强指针来访问
- (void)test { Person *person = [[Person alloc] init]; person.age = 30; person->_name = @"yang"; __weak typeof(person) weakPer = person; person.myblock = ^{ // 其实就是骗编译器 block 内部引用的还是 weakPer __strong typeof(weakPer) strongPer = weakPer; NSLog(@"%d", weakPer.age); NSLog(@"%@", strongPer->_name); }; NSLog(@"%@",[person.myblock class]); person.myblock(); }
输出
__NSMallocBlock__ 30 yang -[Person dealloc] -[VC dealloc]
clang
struct __VC__test_block_impl_0 { struct __block_impl impl; struct __VC__test_block_desc_0* Desc; Person *__weak weakPer; __VC__test_block_impl_0(void *fp, struct __VC__test_block_desc_0 *desc, Person *__weak _weakPer, int flags=0) : weakPer(_weakPer) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };
block 内部成员变量weakPer还是弱引用Person实例对象 是weak类型,并不受block代码块内部的__strong转化,该转化只是为了骗取编译器通过编译而已
例子7 没有循环引用
@interface VC () @property (nonatomic, copy) NSString *name; @end @implementation VC - (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = [UIColor whiteColor]; self.name = @"yang"; void(^block)(void) = ^{ NSLog(@"%@",self.name); }; NSLog(@"block类型%@",[block class]); block(); } - (void)dealloc { NSLog(@"vc dealloc"); }
输出
block类型__NSMallocBlock__ yang vc dealloc
clang
// vc 不持有 block struct VC_IMPL { struct UIViewController_IMPL UIViewController_IVARS; NSString *_name; }; static void _I_VC_viewDidLoad(VC * __strong self, SEL _cmd) { void(*block)(void) = ((void (*)())&__VC__viewDidLoad_block_impl_0((void *)__VC__viewDidLoad_block_func_0, &__VC__viewDidLoad_block_desc_0_DATA, self, 570425344)); } struct __VC__viewDidLoad_block_impl_0 { struct __block_impl impl; struct __VC__viewDidLoad_block_desc_0* Desc; VC *const __strong self; __VC__viewDidLoad_block_impl_0(void *fp, struct __VC__viewDidLoad_block_desc_0 *desc, VC *const __strong _self, int flags=0) : self(_self) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } };
分析
block持有self 强引用self
self不持有block 不构成循环引用总结
-
block对象与OC对象相互持有(强引用) 才会造成相互循环引用
-
block对象持有__block变量对象 __block变量对象持有oc对象 oc对象持有block对象 构成3角循环引用
-
循环引用会导致实例对象不能释放 也就是实例对象所占用的内存不能及时被系统回收,会造成内存泄漏。
-
-
自动引用计数与循环引用
2022-07-20 20:06:32自动引用计数和循环引用实例分析本文主要讲解 自动引用计数和循环引用 这两个大问题。
对于自动引用计数,没有什么争议。
而对于循环引用,这里主要是讲Object-C语言下的循环引用, 因为据我了解,Swift语言下也有循环引用。这两者根本原因是一致的,但解决方法有很大的差异。 所以这里特别说明是Object-C语言下的循环引用。对于Swift下的循环引用,以后再讲解。
自动引用计数
概念
说自动引用计数之前,先说下引用计数,引用计数是苹果公司设计出来,用来跟踪和管理App的内存情况的一套机制。它的运行机制大概是,当创建一个类对象,引用计数就为1,retain一次,计数+1,release一次,计数-1, 当计数减为0,系统就释放该对象,内存也回收,实现了对App的内存管理。
引用计数又分为:手动引用计数(Manual Reference Count)和自动引用计数(Auto Reference Count),前者简称MRC,后者为ARC。
MRC在iOS开发前期使用。主要由开发人员来管理引用计数。即要用的时候,retain一次,要释放的时候release一次。直到引用计数为零,系统才会释放对象,回收内存。
ARC是后来才推出的内存管理机制,它简化了流程。引用计数不需要开发人员来管理和维护了,全由系统帮助完成。 简单的来说,ARC无须开发人员考虑内存的管理情况,它会在类对象被引用的时候,引用计数+1;在release的时候,引用计数-1;在类对象不再使用的时候,自动释放其占用的内存。让开发人员从复杂的内存管理中解脱出来,大大地提高了开发人员的效率。
显然地,ARC更好用。那这么好用的ARC,我们再深入讲下它的工作机制。
自动引用计数的工作机制
正如前面所说一样,每当创建一个新的类对象时,ARC就会分配一块内存来储存该对象的信息。内存中会包含对象的类型信息,同时引用计数器会+1 。
当其他对象引用这个类对象时,计数再+1,若一直引用,那引用计数就一直累加。
当引用的类对象设置为nil,内部实现就是调用release一次,引用计数就会-1。多个类对象设置为nil, 就会减少多个计数值。
当对象不再使用了,ARC 就会释放对象所占用的内存,并让释放的内存能挪作他用。这确保了不再被使用的对象,不会一直占用内存空间。当然它的判断条件就是引用计数是否为零。为零就释放内存。
当 ARC 释放了类对象,类对象所对应的方法和属性将不能再被调用。否则App就会崩溃。
自动引用计数内部实现机制
在引用计数的底层实现中,是通过维护一个引用计数表来跟踪和计算每一个对象的引用情况。其中表里的key为内存块地址的散列值,value为该对象的引用计数。
每引用一次,value的值就+1, 同样的release一次,value值就-1,只有value值为0,才会销毁该对象并从引用计数表移除该key和value。
上述的对象引用过程,其实就是对对象的强引用。之所以称之为“强”引用,是因为它会将对象牢牢地保持住,只要强引用还在,对象是不允许被销毁的。
在实际开发过程中,声明一个类对象,默认就是强引用。
Person *person1;
该声明等价于
__strong Person *person1;
自动引用计数的实践
下面的例子展示ARC的工作机制。
1.创建一个Person类,定义name属性。在init和dealloc函数上分别打印init和dealloc的日志。
#import <Foundation/Foundation.h>
@interface Person : NSObject {
}
@property(nonatomic,retain) NSString *name;
@end
@implementation Person
- (id)init {
self = [super init];
NSLog(@"Person is being init");
return self;
}
- (void)dealloc {
NSLog(@"Person is being dealloc");
}
@end
2.然后在ViewdidLoad函数中定义 三个对象
Person *person1;
Person *person2;
Person *person3;
由于三个对象没有赋值,所以为空的。
这三个对象是强引用关系,因为它们等价于
__strong Person *person1;
__strong Person *person2;
__strong Person *person3;
3.接着创建Person对象并赋值给person1。
person1 = [[Person alloc] init];
通过这一段代码创建了一个Person对象,并把对象赋值给person1。也就是说person1强引用了Person对象。
在引用计数表中,就实现了+1操作。
4.接着在把person1赋值给person2,person3。实现person2,person3的强引用。
person2 = person1;
person3 = person1;
注:在实际开发中不会这么分解写代码。这里只是方便解说。正常的写法是2,3,4一起完成,如下代码所示:
Person *person1 = [[Person alloc] init];
Person *person2 = person1;
Person *person3 = person1;
5.对person1和person2对象进行释放操作。
person1 = nil;
person2 = nil;
通过这两行代码操作,Person对象内存释放吗? 显然没有。因为person3还持有该引用。
6.最后,person3设置为nil, 引用计数为0,ARC 才会销毁Person对象,回收内存。
以上就是自动引用计数的使用过程。
类对象间的循环引用
在上面的例子中,ARC 会跟踪新创建的Person对象的引用计数,并且会在 Person对象不再被需要时销毁它。
然而,我们可能会写出一个类对象的引用计数永远不能为0 的代码。即如果两个对象互相持有对方,每个对象都让对方一直存在,这种情况就是所谓的循环引用。
下面以具体类来说明下。
1.新建两个类,Person和Car
Class Car;
@interface Person : NSObject {
}
@property(nonatomic,retain) NSString *name;
@property(nonatomic,retain) Car *car;
@end
Class Person;
@interface Car : NSObject {
}
@property(nonatomic,retain) NSString *color;
@property(nonatomic,retain) Person *person;
@end
上述Person类中有两个属性,name表示“Person”对应的姓名,car表示“Person”对应的一辆车。假设这“Person”对象只有姓名和车。
同样地,Car对象下,color和person属性,对应的就是车的款式(以颜色代替)和车主。
2.创建类对应的实例并给属性赋值。
Person * john = [[Person alloc] init];
Car * johnCar = [[Car alloc] init];
john.name = “John”;
john.car = johnCar;
johnCar.color = “red”;
johnCar.person = john;
以上代码,在两个对象被创建和赋值后,就表现了强引用的关系。对象john现在有一个指向Car对象的强引用(johnCar),而变对象johnCar 有一个指向 Person 对象的强引用(car)。如下图。
同样地,这种强引用关系就包含了循环引用。即john对象持有了johnCar,johnCar也持有了john。 如下图。
当我们要释放john 和johnCar时,引用计数并不会降为 0,对象也不会被 ARC 销毁,这样就导致了内存泄露。
john = nil;
johnCar = nil;
未释放对象。
解决类对象的循环引用
针对上面的问题,在OC中,主要使用弱引用来解决循环引用问题。
弱引用
弱引用不会对其引用的对象进行强引用,因而不会阻止 ARC 销毁被引用的对象。
这个特性阻止了引用变为循环引用。我们只要在声明属性或者变量时,在前面加上 weak 关键字,就表明这是一个弱引用。
在底层的实现上,系统维护着一个弱引用表,每一次声明弱应用变量或属性都会在这张表中登记, 当对弱引用变量赋值时,就在这张表中建立起弱引用与对象之间的关系。有多少次赋值,就会建立多少次弱引用关系。
由于弱引用不会持有所引用的对象,即使引用存在,对象也有可能被销毁。因此,ARC 会在引用的对象被销毁后自动将其弱引用赋值为 nil,这个操作是为了对象的安全。
这样,对于上述的john和johnCar的循环引用问题,我们只要一方使用弱引用,就可以解除循环引用的问题。如下图所示:
在person释放的时候,可以不用等待Car的持有关系,因为它是弱引用。这种方式就解决了循环引用的问题,避免了内存泄露。
最后说明下:
关于循环引用有:1.类对象的循环引用;2.委托的循环引用;3.block的循环引用三个场景。
这次主要介绍类对象的循环引用和解决方法。
对于委托的循环引用,根本原因和类对象一样的(相互持有),解决方法就是在声明处使用weak关键字就可以了。
对于block的循环引用,内容比较多,见我的另一文章【探究Block底层原理(三)】
-
Python对象的循环引用问题
2020-12-08 18:17:52Python对象循环引用我们来介绍一下 Python 是采用何种途径解决循环引用问题的。循环引用垃圾回收算法上图中,表示的是对象之间的引用关系,从自对象指向他对象的引用用黑色箭头表示。每个对象里都有计数器。而图中...Python对象循环引用
我们来介绍一下 Python 是采用何种途径解决循环引用问题的。
循环引用垃圾回收算法
上图中,表示的是对象之间的引用关系,从自对象指向他对象的引用用黑色箭头表示。每个对象里都有计数器。而图中右侧部分可以很清晰的看到是循环引用的垃圾对象。
上图,将每个对象的引用计数器复制到自己的另一个存储空间中。
上图其实和图二(图片左上角)没什么区别,只不过更清晰了。因为对象本来就是由对象链表连接的。只不过是把对象链表画了出来。
上图中,将新复制的计数器都进行了减量的操作。先不要管为什么,继续往下看。
但是可以看到,由根直接引用的对象中,新复制的计数器并没有减量。
以上操作执行完毕后,再把对象分为可能到达的对象链表和不可能到达的对象链表。
之后将具备如下条件的对象连接到“可能到达对象的链表”。
经过 (4) 的减量操作后计数器值大于等于 1。
有从活动对象的引用。
再将具备如下条件的对象连接到“不可能到达对象的链表”。
经过 (4) 的减量操作后计数器值为 0
没有从活动对象的引用
现在上图显示的就是垃圾对象链表和活动对象的链表了。接下来的步骤就是释放不可能到达的对象,再把可能到达的对象连接到对象链表。
这样,Python中只要将“部分标记-清除算法”稍加变形,就解决了循环引用问题。
容器对象
并不是所有的Python对象都会发生循环引用。有些对象可能保留了指向其他对象的引用,这些对象可能引起循环引用。
这些可以保留了指向其他对象的引用的对象 就被称为容器对象。具有代表性的就是元组,字典,列表。
非容器对象有字符串和数值等。这些对象不能保留指向其他对象的引用。
容器对象中都被分配了用于循环引用垃圾回收的头结构体
这个用于循环引用垃圾回收的头包含以下信息:
用于容器对象双向链表的成员。
用于复制引用计数器的成员。
定义如下:Include/objimpl.h
typedef union _gc_head {
struct {
union _gc_head *gc_next; /* 用于双向链表 */
union _gc_head *gc_prev; /* 用于双向链表 */
Py_ssize_t gc_refs; /* 用于复制 */
} gc;
long double dummy;
} PyGC_Head;
结构体 PyGC_Head 里面是结构体 gc 和成员 dummy 的联合体。
在这里成员 dummy 起到了一定的作用:即使结构体 gc 的大小为9字节这样不上不下的 数值,它也会将整个结构体 PyGC_Head 的大小对齐为 long double 型。因为结构体 gc 的大 小不太可能变成这样不上不下的数值,所以事实上 dummy 起到了一个以防万一的作用。
生成容器对象
在生成容器对象时,必须分配用于循环引用垃圾回收的头,在这里由 _PyObject_GC_Malloc() 函数来执行分配头的操作。这个函数是负责分配所 有容器对象的函数。
Modules/gcmodule.c: _PyObject_GC_Malloc():只有分配头的部分
PyObject * _PyObject_GC_Malloc(size_t basicsize)
{
PyObject *op;
PyGC_Head *g;
g = (PyGC_Head *)PyObject_MALLOC(
sizeof(PyGC_Head) + basicsize);
g->gc.gc_refs = GC_UNTRACKED;
/* 开始进行循环引用垃圾回收:后述 */
op = FROM_GC(g);
return op; }
1.首先分配对象,于此同时分配了结构体PyGC_Head。
2.将GC_UNTRACKED存入用于循环引用垃圾回收的头内成员gc_refs中。当出现这个标志的时候,GC会认为这个容器对象没有被连接到对象链表。
define _PyGC_REFS_UNTRACKED (-2)
这个_PyGC_REFS_UNTRACKED是GC_UNTRACKED的别名。gc_ref是用于复制对象的引用计数器的成员,不过它是用负值作为标识的。再次说明这里这样做,补另建立对象做这件事情是为了减轻负担。
3.最后调用了宏FROM_GC()返回结果。
define FROM_GC(g) ((PyObject *)(((PyGC_Head *)g)+1))
这个宏会偏移用于循环引用垃圾回收的头的长度,返回正确的对象地址。这是因为这项操作,调用方才不用区别对待有循环引用垃圾回收头的容器对象和其他对象。
如果结构体 PyGC_Head 的大小没有对齐,FROM_GC() 返回的地址就是没有被对齐的不上 不下的值,因此需要按合适的大小对齐结构体 PyGC_Head 的大小。
追踪容器对象
为了释放循环引用,需要将容器对象用对象链表连接(双向)。再生成容器对象之后就要马上连接链表。下面以字典对象为例:
PyObject * PyDict_New(void)
{
register PyDictObject *mp;
/* 生成对象的操作 */
_PyObject_GC_TRACK(mp);
return (PyObject *)mp;
}
_PyObject_GC_TRACK() 负责连接链表的操作。
#define _PyObject_GC_TRACK(o) do {
PyGC_Head *g = _Py_AS_GC(o);
g->gc.gc_refs = _PyGC_REFS_REACHABLE;
g->gc.gc_next = _PyGC_generation0;
g->gc.gc_prev = _PyGC_generation0->gc.gc_prev; g->gc.gc_prev->gc.gc_next = g;
_PyGC_generation0->gc.gc_prev = g;
} while (0);
这个宏里有一点需要注意的,那就是do--while循环。这里不是为了循环,而是写宏的技巧。读代码时可以将其无视。
我们来看看宏内部_Py_AS_GC()的定义如下:#define _Py_AS_GC(o) ((PyGC_Head *)(o)-1)
首先从对象的开头,将头地址偏移相应的大小,取出循环引用垃圾回收的头。
_PyGC_REFS_REACHABLE 这个标志存入成员 gc_refs 中。标志程序可能到达对象的意思。
最后全局性容器对象链表(拥有所有容器对象的全局性容器),把对象连接到这个链表。
这样一来就把所有容器对象都连接到了作为容器对象链表的双向链表中。循环引用垃圾回收就是用这个容器对象链表来释放循环引用对象的。
结束追踪容器对象
通过引用计数法释放容器对象之前,要把容器对象从容器对象链表中取出。因为呢没有必要去追踪已经释放了的对象,所以这么做是理所应当的。下面以字典对象为例释放字典的函数。
使用PyObject_GC_UnTrack() 函数来执行结束追踪对象的操作
IS_TRACKED()包含在 PyObject_GC_UnTrack(),判断对象是不是正在追踪的对象
AS_GC() 是之前讲过的宏 _Py_AS_GC() 的别名,包含在IS_TRACKED()里。用于判断对象是不是正在追踪,如果是就结束追踪。
_PyGC_REFS_UNTRACKED 这里只是将追踪对象以外的标志存入成员gc_refs,并从容 器对象链表中去除而已。
大多数情况下是通过引用计数法的减量操作来释放容器对象的,因为循环引用垃圾回收释放的知识具有循环引用关系的对象群,所以数量并没有那么多。
分代容器对象链表
容器对象链表分为三代。循环引用垃圾回收事实上是分带垃圾回收。
系统通过下面的结构体来管理各代容器对象链表。
struct gc_generation {
PyGC_Head head;
int threshold; /* 开始GC的阈值 */
int count; /* 该代的对象数 */
};
首先将容器对象连接到成员head
设置threshold阈值
当count大于阈值的时候启动GC。
不同的代阈值是不同的,count也是不同的。
一开始所有容器对象都连接0代对象。之后只有经过循环引用垃圾回收的对象活下来一定次数才能够晋升。
何时执行循环引用垃圾回收
在生成容器对象的时候执行循环引用垃圾回收。代码如下:
Modules/gcmodule.c
PyObject * _PyObject_GC_Malloc(size_t basicsize)
{
PyObject *op;
PyGC_Head *g;
/* 生成对象的操作 */
/* 对分配的对象数进行增量操作 */
generations[0].count++;
if (generations[0].count > generations[0].threshold && enabled && generations[0].threshold && !collecting && !PyErr_Occurred()) {
collecting = 1;
collect_generations();
collecting = 0;
} op = FROM_GC(g);
return op;
}
先进性对0代成员count执行增量操作。generations[0].count++;
接下来检测0代的count有没有超过阈值。
接着确认全局变量enabled是0以外的数值。只有在用户不想运行循环引用垃圾回收的时候,才为0.通过python就可进行设置。
确认threshold不为0.
确认循环引用垃圾回收是否正在执行。
最后执行PyErr_Occurred()函数检测有没有发生异常。
如果检测全部合格,就开始执行循环引用的垃圾回收。
在循环引用的垃圾回收时,将全局变量collecting设置1,调用collect_generations()函数。这就是调用循环引用垃圾回收的部分。
Modules/gcmodule.c
static Py_ssize_t collect_generations(void)
{
int i;
Py_ssize_t n = 0;
for (i = NUM_GENERATIONS-1; i >= 0; i--) {
if (generations[i].count > generations[i].threshold) {
n = collect(i); /* 执行循环引用垃圾回收! */ break;
}
}
return n;
}
在这里检查各代的计数器和阈值,对超过阈值的代执行GC,这样一来循环引用垃圾回 收的所有内容就都装入了程序调用的 collect() 函数里。
循环引用的垃圾回收
来看一下collect()Modules/gcmodule.c
static Py_ssize_t collect(int generation)
{
int i;
PyGC_Head *young; /* 即将查找的一代 */
PyGC_Head *old; /* 下一代 */
PyGC_Head unreachable; /* 无异样不能到达对象的链表 */
PyGC_Head finalizers;
/* 更新计数器 */
if (generation+1 < NUM_GENERATIONS) generations[generation+1].count += 1;
for (i = 0; i <= generation; i++)
generations[i].count = 0;
/* 合并指定的代及其以下的代的链表 */
for (i = 0; i < generation; i++) {
gc_list_merge(GEN_HEAD(i), GEN_HEAD(generation));
}
/* 给old变量赋值 */
young = GEN_HEAD(generation);
if (generation < NUM_GENERATIONS-1)
old = GEN_HEAD(generation+1);
else
old = young;
update_refs(young); /*把引用计数器复制到用于循环引用垃圾回收的头里 */
subtract_refs(young); /* 删除实际的引用 */
/* 将计数器值为0的对象移动到不可能到达对象的链表 */ gc_list_init(&unreachable);
move_unreachable(young, &unreachable);
/* 将从循环引用垃圾回收中幸存的对象移动到下一代 */
if (young != old)
gc_list_merge(young, old);
/* 移出不可能到达对象的链表内有终结器的对象 */ gc_list_init(&finalizers);
move_finalizers(&unreachable, &finalizers); move_finalizer_reachable(&finalizers);
/* 释放循环引用的对象群 */
delete_garbage(&unreachable, old);
/* 将finalizers链表注册为“不能释放的垃圾” */ (void)handle_finalizers(&finalizers, old);
}
首先将一个老年代的技术局执行增量操作,将制定的代的计数器设置为0。之后所指定的代及其以下代的链表合并到自己所属的代。
然后把引用计数器复制到用于循环引用垃圾回收的头里。从这个计数器删除实际的引用。循环引用的对象的计数器值会变为0。
之后把从GC中幸存下来的对象联通链表一起合并到下一代。让其晋升。
由于某些原因,程序无法释放有终结器的循环引用对象,所以要将其移除。
循环引用中的终结器
循环引用垃圾回收把带有终结器的对象排除在处理范围之外。这是为什么?
当然是因为太复杂了。哈哈
举个栗子假设两个对象是循环引用关系,如果他们都有自己的终结器那么先调用那个好?
在第一个对象最终化后,第二个对象也最终化。那么或许在最终化的过程中又用到了第一个对象。也就是说我们绝对不能先最终化第一个对象。
所以在循环引用的垃圾回收中,有终结器的循环引用垃圾对象是排除在GC的对像范围之外的。
但是有终结器的循环引用对象,能够作为链表在Python内进行处理。如果出现有终结器的循环引用垃圾对象,我们就需要利用这项功能,从应用程序的角度去除对象的循环引用。
python关于GC的模块
gc.set_debug()(可以查看垃圾回收的信息,进而优化程序)
Python采用引用计数法,所以回收会比较快。但是在面临循环引用的问题时候,可能要多费一些时间。
在这种情况下,我们可以使用gc模块的set_debug()来查找原因,进而进行优化程序。
import gc
gc.set_debug(gc.DEBUG_STATS)
gc.collect()
# gc: collecting generation 2...
# gc: objects in each generation: 10 0 13607
# gc: done, 0.0087s elapsed.
一旦用set_debug()设定了gc.DEBUG_STATS标志,那么每次进行循环引用垃圾回收,就会输出一下信息。
1. GC 对象的代
2. 各代内对象的数量
3. 循环引用垃圾回收所花费的时间
当然除了DEBUG_STATS以外,还可以设置各种标志,关于这些标志可以查看源码或者官方文档。
gc.collect()
经过第一步的优化后,如果还是不行,就要用到gc.collect()。
使用gc.collect()就能在应用程序运行的过程中,任意时刻执行循环引用垃圾回收了。
也就是说,我们人为的选择最合适的时间去进行循环引用的GC。
gc.disable()
一旦调用 gc.disable(),循环引用垃圾回收就停止运作了。也就是说,循环引用的垃圾对象群一直不会得到释放。 然而从应用程序整体的角度来看,如果循环引用的对象的大小可以忽视,那么这个方法 也不失为一个好方法。这就需要我们自己来权衡了。
-
iOS之深入解析如何检测“循环引用”
2022-04-01 18:25:57引用计数固然有其优越性,但也正是因为缺乏对全局对象信息的把控,导致 Objective-C 无法自动销毁陷入循环引用的对象。虽然 Objective-C 通过引入弱引用技术,让开发者可以尽可能地规避这个问题,但在引用层级过深,... -
怎么解决引用计数 GC 的循环引用问题?
2021-03-15 15:25:15思考一个问题:不通过 GC ROOT,仍使用引用计数方式,怎么解决它的循环引用问题?解答此问题前,通过目标驱动法来想象一下,若 Get 了此知识点,可以这样应用到面试中:面试官: 说一下垃圾回收机制吧我... -
Python 垃圾回收机制和如何解决循环引用
2021-02-03 14:53:21引用计数:是一种垃圾收集机制,而且也是一种最直观,最简单的垃圾收集技术, 当一个对象的引用被创建或者复制时,对象的引用计数加 1;当一个对象的引用被销毁时,对象的引用计数减 1;当对象的引用计数减少为 0 时... -
【方向盘】Spring Boot 2.6.0正式发布,循环引用终于被禁
2021-12-12 21:00:33禁止循环引用、可自定义脱敏规则、启用PathPattern等都是亮点 -
填坑,解决json对象循环引用,在复杂案例中的应用
2021-02-28 13:11:33// 这是Kotlin代码,Java也...// 所以一对多/多对多的循环引用问题都能解决,从任意方向开始引用的都行。//// 由于这种玩法似乎挤占了JsonView的原有用途,所以是有代价的,本篇讨论了代价及如何避坑。// 高级篇:模... -
circle-json:JSON不处理循环引用。 现在可以了
2021-03-01 17:29:58序列化和反序列化其他有效的JSON对象,这些对象包含与专用JSON格式之间的循环引用。 该模块的未来称为 更小,更快,并能生产平均减小的输出太,是新的,bloatless,ESM和CJS兼容,圆形JSON解析器。 现在已经达到V1... -
如何解决引用计数的循环引用问题
2020-03-28 17:06:43循环引用 public class MyObject { public Object ref = null; public static void main(String[] args) { MyObject myObject1 = new MyObject(); MyObject myObject2 = new MyObject(); ... -
JS 判断对象内是否有循环引用
2021-05-08 11:06:04判断一个对象内是否有循环引用 循环引用定义 循环引用是指对象中的属性指向对象本身,如果属性内部的深层属性指向对象本身,也属于循环引用。如 // 循环引用 const a = {}; a.b = a &... -
JavaScript循环引用 浅谈
2021-01-26 14:23:40循环引用指的是对象A 中包含一个指向对象B的指针,而对象B中也包含一个指向对象A的引用。比如下面这个例子: function problem(){ var objectA = new Object(); var objectB = new Object(); objectA.someOther... -
循环引用导致的问题与解决方案
2021-09-01 01:31:21点击上方“服务端思维”,选择“设为星标”回复”669“获取独家整理的精选资料集回复”加群“加入全国服务端高端社群「后端圈」作者 | 谨寻出品| 淘系技术返回结果中存在循环引用可能导致的问... -
引用计数法的循环引用问题
2018-10-15 17:13:20关于引用计数法,我们可以先看一段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... -
利用接口解决Java工程间循环引用而报错的问题 | 学步园
2021-03-06 01:17:20解决Eclipse中Java工程间循环引用而报错的问题如果我们的项目包含多个工程(project),而它们之间又是循环引用的关系,那么Eclipse在编译时会抛出如下一个错误信息:“A cycle was detected in the build path of ... -
智能指针循环引用
2020-09-05 16:26:47C++11引入了智能指针unique_ptr,shared_ptr,及weak_ptr来...对于shared_ptr,它内部有一个引用计数变量,每当它被拷贝一次,这个变量就加1。同样地,每当它被析构,变量就减1。当引用计数变量变为0时,就将它所指向的 -
C #include 循环引用问题, 头文件循环引用
2021-05-23 06:11:05例子1说明: header1.h 包含 header2.h; header2.h 包含 header1.h;/**circulardependency--测试循环引用*/#include#include"header1.h"intmain(void){printf("thisismyfunction!");return0;}header1.h#ifndef... -
vue组件之间的循环引用
2022-05-17 17:34:06项目场景: 点击表格中的一个链接,打开数据详情的...组件的循环嵌套,对应vue官方的处理边界情况-组件之间的循环引用 解决方案: 方案一:复制一份机构管理页面组件到详情页组件中 方案二:异步调用组件 component -
XML序列化包含循环引用的解决方法以及.net和Java值/引用传递的区别
2019-03-10 13:44:22解决方法是,在循环引用的字段前面加上一段修饰语句 [System.Xml.Serialization.XmlIgnore] 这样在序列化的时候,系统会自动忽略该字段实例的序列化 .net与Java最大的不同在于,XML结构的字段是值传递,而.ne..... -
shared_ptr循环引用
2021-03-15 15:56:04循环引用:两个对象相互使用shared_ptr成员变量指向对方造成循环引用,导致引用计数失效。 即A内部有指向B,B内部有指向A,这样对于A,B必定是在A析构后B才析构,对于B,A必定是B析构后才析构A,这就是循环引用的... -
Swift之常见闭包与defer关键字的使用分析和闭包中的循环引用
2022-05-09 13:48:47一、什么是闭包? 在 Swift 中,可以通过 func 定义一个函数,也可以通过闭包表达式定义一个函数,闭包是一个捕获了上下文的常量或者是变量的函数。闭包(Closures)是自包含的功能代码块,可以在代码中使用或者用来... -
Fastjson循环地址引用
2021-03-23 18:14:37Fastjson循环地址引用前言一、Fastjson循环地址引用产生原因?二、解决方式1.配置SerializerFeature总结 前言 一、Fastjson循环地址引用产生原因? 在查看源代码的情况下,发现在每次拼接json数据的时候,...