Python with 工作原理、装饰器、回收机制、内存管理机制、拷贝、作用域等( 十 )


当一个对象不再调用的时候 , 也就是当这个对象的引用计数(指针数)为 0 的时候 , 说明这个对象永不可达 , 自然它也就成为了垃圾 , 需要被回收 。可以简单的理解为没有任何变量再指向它 。
调用函数 func() , 在列表 a 被创建之后 , 内存占用迅速增加到了 433 MB:而在函数调用结束后 , 内存则返回正常 。这是因为 , 函数内部声明的列表 a 是局部变量 , 在函数返回后 , 局部变量的引用会注销掉;此时 , 列表 a 所指代对象的引用数为 0 ,  便会执行垃圾回收 , 因此之前占用的大量内存就又回来了 。
新的这段代码中 ,  a 表示将 a 声明为全局变量 。那么 , 即使函数返回后 , 列表的引用依然存在 , 于是对象就不会被垃圾回收掉 , 依然占用大量内存 。同样 , 如果我们把生成的列表返回 , 然后在主程序中接收 , 那么引用依然存在 , 垃圾回收就不会被触发 , 大量内存仍然被占用着:
那怎么可以看到变量被引用了多少次呢?通过 sys.
如果其中涉及函数调用 , 会额外增加两次 1. 函数栈 2. 函数调用
从这里就可以看到不再需要像C那种的人为的释放内存 , 但是同样给我们提供了手动释放内存的方法 gc.()
截止目前 , 貌似的垃圾回收机制非常的简单 , 只要对象引用次数为0 , 必定为触发gc , 那么引用次数为0是否是触发gc的充要条件呢?
垃圾回收机制:循环回收
如果有两个对象 , 它们互相引用 , 并且不再被别的对象所引用 , 那么它们应该被垃圾回收吗?
从结果显而易见 , 它们并没有被回收 , 但是从程序上来看 , 当这个函数结束的时候 , 作为局部变量的a , b就已经从程序意义上不存在了 。但是因为它们的互相引用 , 导致了它们的引用数都不为0 。
这时要如何规避呢?
从代码逻辑上进行整改 , 避免这种循环引用通过人工回收
针对循环引用 , 有它的自动垃圾回收算法:1. 标记清除(mark-sweep)算法 2. 分代收集()
垃圾回收机制:标记清除
标记清除的步骤总结为如下步骤:
GC会把所有的『活动对象』打上标记
把那些没有标记的对象『非活动对象』进行回收
那么如何判断何为非活动对象?
通过用图论来理解不可达的概念 。对于一个有向图 , 如果从一个节点出发进行遍历 , 并标记其经过的所有节点;那么 , 在遍历结束后 , 所有没有被标记的节点 , 我们就称之为不可达节点 。显而易见 , 这些节点的存在是没有任何意义的 , 自然的 , 我们就需要对它们进行垃圾回收 。
但是每次都遍历全图 , 对于而言是一种巨大的性能浪费 。所以 , 在的垃圾回收实现中 , mark-sweep 使用双向链表维护了一个数据结构 , 并且只考虑容器类的对象(只有容器类对象 , list、dict、tuple ,  , 才有可能产生循环引用) 。
容器对象(比如:list , set , dict , class , )都可以包含对其他对象的引用 , 所以都可能产生循环引用 。而“标记-清除”计数就是为了解决循环引用的问题 。
在了解标记清除算法前 , 我们需要明确一点 , 关于变量的存储 , 内存中有两块区域:堆区与栈区 , 在定义变量时 , 变量名与值内存地址的关联关系存放于栈区 , 变量值存放于堆区 , 内存管理回收的则是堆区的内容 , 详解如下图