垃圾回收机制指的是删除程序中不再使用的对象,防止占用大量不必要的内存,引起内存泄漏。
这里的对象,万物皆对象。组合数据类型、对象等都在被回收的对象范围之中。
这里的不再使用指的是程序中没有指向该对象的变量或者没有容器(组合数据类型、对象)包含该对象,此时该对象就应该被回收;
如何判断对象是否应该被回收?
使用引用计数原则和标记-清理、分代收集机制。前者为主,后者为辅;
引用指的是当有一个变量指向该对象或者有个容器包含该对象时,变量或容器在内存中会存储该对象的内存地址。访问时,去变量内存中取出变量内存中存储的对象地址,再去被找到的地址获得变量。
python会在传见每一个对象时分配一个计数。计数随着被引用的次数增加。
增加的情况如下:1.对象被传见时
2.对象被复制或浅copy
3.对象被作为参数传入到函数时
4.对象作为子元素,存储到元素中如:lista = [1];Goods().a = 0
引用计数会增加也要减少,才能被回收。当计数减少为0时,该对象就会被回收机制真正删除(调用del 只是显示删除,并不一定真正被内存删除)。
引用计数减少的情况:1.del(显示删除)
2.对象的引用被指向新的对象,则旧的对象计数-1,新的+1
3.函数执行完成后,形参失去意义。
4.对象所在的容器被真正的删除,或者将对象从容器中删除。如del [1,2,[1,2]][2](删除[1,2,[1,2]]中第三个元素)
随着计数的减少,直到为0,该对象被真正删除。但循环调用是一种特殊情况,无法使计数减少到0;此时,需要借助标记-清理原则。
标记清理:
针对某些代数较低的对象,当del 被调用后,计数会减一。若此时计数不等于0,则强制使计数减一。若此时计数为0,将对应对象放入死亡容器,否则,放入存活容器。然后遍历两个容器,若死亡容器有被引用的对象,将该对象放入存活容器。最后删除死亡容器内的所有剩余对象。
以上两个机制,可以删除对象节省资源。那么,什么时候去执行标记清理呢?
当创建对象后该对象在存活池内,当然里边还有存活的对象。新穿件的对象为0代,执行完一次标记清理后存活池中所有对象代数加一。代数越多,表示存活时间久,容易被调用,执行标记删除时间间隔就久,不会是计数减1。
分带集收
引用计数优缺点:
优:简单直观,实时释放资源
缺:维护计数需要不停计算耗费资源、循环调用无法收回内存。
循环调用为什么无法收回内存?
假设内存a存储一个列表的地址001。那么001中存放列表第一个元素地址010,010存储另一个列表的首地址,的003存储第二个元素地址012...此时,删除列表那么内存a中仍然不再存储001,然而,001中仍存在010,010仍然存储另一个列表的首地址,那么,列表中数据在没有符合计数为0的要求下就不会被清空,也就是说,另一个列表的引用不会减少。而由于另一个列表也引用了该列表,形成了循环调用,则无法真正符合计数为0的要求,内存无法回收。
解决循环调用占用内存的两种方法:
1.标记-清除
2.先删除被引用的内部元素,再删除外部对象。
在使用开发工具后,穿件对象并不是理想情况下计数为1,因此只需要做到使穿件时的计数与被显示删除时计数一致即可