1.引用计数
import os
import psutil
def show_memory_info(hint):
pid = os.getpid()
p = psutil.Process(pid)
info = p.memory_full_info()
memory = info.uss / 1024 / 1024
print('==>',memory)
def func():
show_memory_info('in func 0')
a = [i for i in range(1000000)]
show_memory_info('in func 1')
func()
show_memory_info('end')
====》
==> 7.3203125
==> 46.0546875
==> 7.53515625
以上可见:局部变量在函数func内部创建后,内存增加,
函数执行完,局部变量的引用会注销掉--a所指代的对象引用数为0,即内存被回收
若将啊改为全局变量则func函数被调用完后不会被回收
手动释放
先调用del a ; 再调用gc.collect()即可手动启动垃圾回收
2.标记清除(mark-sweep)
举例:对于一个有向图,如果从一个节点出发遍历,并标记每个经过的节点,那么在遍历结束后,所有没有被标记的节点,我们就称之为不可达节点。不可达节点会被回收。
mark-sweep使用双向链表维护一个数据结构,并值考虑容器类对象。
这里,我们介绍一种自动管理堆内存的算法:Mark and Sweep Algorithm(标记清除算法)
Mark and Sweep Algorithm(标记清除算法)
一般而言,垃圾回收算法都会包含两个基本操作。
操作1,检测所有不可达对象;
步骤2,回收不可达对象所占用的堆内存。
Mark and Sweep Algorithm(标记清除算法)在下面两个阶段执行这两个操作:
1)Mark phase(标记阶段)
2)Sweep phase(清除阶段)
Mark phase(标记阶段)
在对象创建时,设置它的标记位为0(false)。在Mark phase(标记阶段),我们设置所有可达对象的标记位为1。这一设置过程可以通过图遍历算法来完成,比如深度优先遍历算法。我们可以将每个对象看作图的一个结点,从每一个可达的结点出发,访问和它相连的没有被访问过的结点,并将这些结点标记为可达,然后递归访问与这些结点相连的没有被访问过的结点。
伪代码如下:
- root变量引用了一个可以被局部变量直接访问的对象。这里我们假设只有一个root变量。
- markedBit(obj)用来访问一个对象的标记位。
Mark(root)
If markedBit(root) = false then
markedBit(root) = true
For each v referenced by root
Mark(v)
提示:如果有多个root变量,对每个root变量调用Mark()即可。
Sweep phase(清除阶段)
这一阶段将回收所有不可达对象所占用的堆内存。所有标记位为false的对象,会被清除,其余标记位为true的对象,会将其标记为设置为false。
伪代码如下:
Sweep()
For each object p in heap
If markedBit(p) = true then
markedBit(p) = false
else
heap.release(p)
3.分代收集(generational)
python将所有对象分为三代。刚创立的对象为第0代;经过一次垃圾回收之后,依然存在的对象便会从上一代挪到下一代。而每一代启动自动垃圾回收的阈值可以单独指定。当垃圾回收器中新增对象减去删除对象达到相应的阈值时,就会对这一代对象启动垃圾回收。
分代收集思想是新生的对象更有可能被垃圾回收,而存活更久的对象有更高概率继续存活,从而减少计算量,提高python性能。
总结:
python运行时维护两张链表:
一是refchain双向链表,存储所有对象创建和引用计数,
另一个只维护可能存在循环引用的对象(容器对象)
python内部会在某些情况下扫描可能循环引用的链表的每个元素,如果存在循环引用,那么让双方的引用计数器都减1,减1后等于0则被垃圾回收
标记清除存在的问题:
1.什么情况下出发
2.多久扫描一次
3.可能存在的循环引用的链表比较大,耗时旧
正对以上问题使用分代回收:
可能存在循环引用的分为3代: 0 代,1代 ,2代。例:第0代对象达800个是启动扫描,0代扫描10则1代扫描1次
参考: