python垃圾回收

在这里插入图片描述

背景:

Python 程序在运行的时候,需要在内存中开辟出一块空间,用于存放运行时产生的临时变量,计算完成后,再将结果输出到永久性存储器中.如果数据量过大,内存空间管理不善就很容易出现 OOM(out of memory),俗称爆内存

简述python垃圾回收机制

python主要通过python垃圾器进行垃圾回收,python的垃圾回收主要基于引用计数,当一个对象的引用计数为0时,它会被回收,同时python还使用了标记清除和分代回收等自动回收算法解决python的循环引用,标记清除指定是python中维护着一个双向链表,通过遍历链表而进行标记,遍历结束,没有被标记的则会被清除,分带回收是指将所有对象分成三代,刚创立的对象是0代,经过一次垃圾回收后依然存在的对象就,便会依次上一代挪到下一代,当垃圾回收器中的新增对象减去删除对象达到相应阈值时,就会启动垃圾回收

1.引用计数

1.内存占用

局部变量

import os
import psutil


# 显示当前 python 程序占用的内存大小
def show_memory_info(hint):
    pid = os.getpid()
    p = psutil.Process(pid)

    info = p.memory_full_info()
    memory = info.uss / 1024. / 1024
    print('{} memory used: {} MB'.format(hint, memory))

def func():
    show_memory_info('initial')
    a = [i for i in range(10000000)]
    show_memory_info('after a created')

func()
show_memory_info('finished')

########## 输出 ##########

initial memory used: 47.19140625 MB
after a created memory used: 433.91015625 MB
finished memory used: 48.109375 MB    

全局变量

def func():
    show_memory_info('initial')
    global a
    a = [i for i in range(10000000)]
    show_memory_info('after a created')

func()
show_memory_info('finished')

########## 输出 ##########

initial memory used: 48.88671875 MB
after a created memory used: 433.94921875 MB
finished memory used: 433.94921875 MB

函数返回值

def func():
    show_memory_info('initial')
    a = [i for i in derange(10000000)]
    show_memory_info('after a created')
    return a

a = func()
show_memory_info('finished')

########## 输出 ##########

initial memory used: 47.96484375 MB
after a created memory used: 434.515625 MB
finished memory used: 434.515625 MB

2.引用次数

1.getrefcount 本身也会引入一次计数

2.函数调用发生的时候,会产生额外的两次引用,一次来自函数栈,另一个是函数参数。

import sys

a = []

# 两次引用,一次来自 a,一次来自 getrefcount
print(sys.getrefcount(a))

def func(a):
    # 四次引用,a,python 的函数调用栈,函数参数,和 getrefcount
    print(sys.getrefcount(a))

func(a)

# 两次引用,一次来自 a,一次来自 getrefcount,函数 func 调用已经不存在
print(sys.getrefcount(a))

########## 输出 ##########

2
4
2

3.手动启动垃圾回收

1.del a 来删除对象的引用

2.gc.collect(),清除没有引用的对象

import gc
show_memory_info('initial')
a = [i for i in range(10000000)]
show_memory_info('after a created')
del a
gc.collect()
show_memory_info('finish')
print(a)

########## 输出 ##########
#
# initial memory used: 48.1015625 MB
# after a created memory used: 434.3828125 MB
# finish memory used: 48.33203125 MB
#
# ---------------------------------------------------------------------------
# NameError                                 Traceback (most recent call last)
# <ipython-input-12-153e15063d8a> in <module>
#      11
#      12 show_memory_info('finish')
# ---> 13 print(a)
#
# NameError: name 'a' is not defined

2.循环引用

1.循环引用含义

如果有两个对象,它们互相引用,并且不再被别的对象所引用,引用计数始终不为0,导致不能被垃圾回收

2.循环引用如何垃圾回收

标记清除(mark-sweep)

对于一个有向图,如果从一个节点出发进行遍历,并标记其经过的所有节点,那么,在遍历结束后,所有没有被标记的节点,我们就称之为不可达节点,当然,每次都遍历全图,对于 Python 而言是一种巨大的性能浪费.所以在Python 的垃圾回收实现中,mark-sweep 使用双向链表维护了一个数据结构,并且只考虑容器类的对象(只有容器类对象才有可能产生循环引用)

分带回收(generational)

Python 将所有对象分为三代。刚刚创立的对象是第 0 代;经过一次垃圾回收后,依然存在的对象,便会依次从上一代挪到下一代。而每一代启动自动垃圾回收的阈值,则是可以单独指定的。当垃圾回收器中新增对象减去删除对象达到相应的阈值时,就会对这一代对象启动垃圾回收。

3.内存泄漏

objgraph一个非常好用的可视化引用关系的包,用于调试内存泄漏,第一个是 show_refs(),它可以生成清晰的引用关系图,另一个非常有用的函数,是 show_backrefs()

import objgraph

a = [1, 2, 3]
b = [4, 5, 6]

a.append(b)
b.append(a)

objgraph.show_refs([a])
objgraph.show_backrefs([a])

思考

实现一个垃圾回收判定算法:输入是一个有向图,给定起点,表示程序入口点,给定有向边,输出不可达节点

from typing import Set


class Graph:
    def __init__(self, value, nodes=None):
        self._value = value
        self._nodes: list = [] if nodes is None else nodes

    @property
    def value(self):
        return self._value

    @property
    def nodes(self):
        return self._nodes

    def node_add(self, node):
        self._nodes.append(node)

    def node_adds(self, nodes):
        self._nodes.extend(nodes)

    def node_del(self, node):
        self._nodes.remove(node)

    def __str__(self):
        return "Graph {} nodes {}".format(self._value, [node.value for node in self.nodes])

    def __repr__(self):
        return self.__str__()


def dfs(start: Graph, includes: Set[Graph] = None) -> Set[Graph]:
    if includes is None:
        includes = set()
    if start in includes:
        return includes
    includes.add(start)
    for s in start.nodes:
        includes.update(dfs(s, includes))
    return includes


if __name__ == '__main__':
    A = Graph('A')
    B = Graph('B')
    C = Graph('C')
    D = Graph('D')
    E = Graph('E')
    F = Graph('F')
    has_nodes = {A, B, C, D, E, F}

    # A->B->E
    # ->C->E
    # B->A
    # D->F
    # F->D
    A.node_adds([B, C])
    B.node_adds([A, E])
    C.node_adds([E])
    D.node_adds([F])
    F.node_adds([D])
    graph_nodes = dfs(A, set())
    # OUT: {Graph B nodes ['A', 'E'], Graph E nodes [], Graph C nodes ['E'], Graph A nodes ['B', 'C']}
    print(graph_nodes)
    # OUT: {Graph F nodes ['D'], Graph D nodes ['F']}
    print(has_nodes - graph_nodes)
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值