python3内存回收__动态类型 / 可变数据类型 / 引用计数 / 引用减少 / 垃圾回收 / 分代回收 / 孤立的引用环

1.动态类型

  1. 对象是存储在内存中的实体。但我们并不能直接接触到该对象。

  2. 引用与对象分离是动态类型的核心。

(一)不可变数据类型:

# --------------------引例1
a = 1
b = a
a = a + 2
print(a, b)

OUTPUT:
--> 3  1


# --------------------引例2
lt = [1, 2, 3]
lt2 = lt
lt = 4
print(lt, lt2)

OUTPUT:
--> 4  [1, 2, 3]


# 说明:
    1.开始a和b为指向1的两个引用
    2.第三个表达式中a重新赋值,指向了新的对象3

# 总结:
    即使多个引用指向同一对象,若一个引用值发生变化,那么实际上是该引用指向一新引用,并不影响其他的引用的指向。

(二)可变数据类型:

以列表为例:

  1. 列表相当于一个引用的集合,每一个元素相当于一个引用(lt[0], lt[1], lt[2])

  2. 下部代码中,lt[0] = 11这一操作,改变的是第一个元素(引用)的指向,而不是lt的指向。故所有指向该lt对象的引用均会受到影响。

 

python3内存回收__动态类型 / 可变数据类型 / 引用计数 / 引用减少 / 垃圾回收 / 分代回收 / 孤立的引用环

lt = [1, 2, 3]
lt2 = lt
lt[0] = 11

print(lt, lt2)

OUTPUT:
--> [11, 2, 3]
--> [11, 2, 3]

2.python内存回收机制

(一)对象的内存使用

  1. python为动态类型编程语言。对象与引用相分离。

  2. id(对象):查看对象的内存地址

  3. python当中会缓存整数、浮点数、字符串、空元组、空集合,并不反复的创建和销毁。例:当创建多个引用引用1时,实际上这些引用均指向同一个对象。

  4. is 关键字用于判断两个引用的对象是否相同。

# python当中会缓存整数、短小字符,并不反复的创建和销毁。例:当创建多个引用引用1时,实际上这些引用均指向同一个对象。
# --------------------1.整数:True
a = 1
b = 1
print(id(a), id(b))
print(a is b)

# --------------------2.浮点数:True
a = 1.0
b = 1.0
print(id(a), id(b))
print(a is b)

# --------------------3.短字符串:True
a = "good"
b = "good"
print(id(a), id(b))
print(a is b)

# --------------------4.字符串:True
a = "very good morning this is linux 123 456 789 10111213"
b = "very good morning this is linux 123 456 789 10111213"
print(id(a), id(b))
print(a is b)

# --------------------5.列表:False
a = [1, 2]
b = [1, 2]
print(id(a), id(b))
print(a is b)

# --------------------6.元组(非空):False
a = (1, 2)
b = (1, 2)
print(id(a), id(b))
print(a is b)

# --------------------7.集合:False
a = set([1, 2])
b = set([1, 2])
print(id(a), id(b))
print(a is b)

# --------------------8.字典:False
a = {"name": "mx"}
b = {"name": "mx"}
print(id(a), id(b))
print(a is b)

(二)引用计数(跟踪和回收垃圾)

  1. 可通过sys包中的getrefcount(引用名)来查看某个对象的引用计数

  2. 当将某个引用作为实参传递给getrefcount()时,参数实际上创建了一个临时的引用。因此,引用计数结果比实际值多1

  3. 对于python的容器(container)对象,如:列表、字典等,其内部包含的并不是对象,而是对象的引用。

  4. 词典对象用于记录所有全局变量的引用。可通过内置函数globals()查看该词典

  5. 容器对象的引用可能会构成很复杂的拓扑结构。可通过objgraph包中的show_refs()函数来进行查看
    6.objgraph包的安装(windows):pip install xdot   /   pip install objgraph
    展示(# 3 中对象引用的拓扑结构):

python3内存回收__动态类型 / 可变数据类型 / 引用计数 / 引用减少 / 垃圾回收 / 分代回收 / 孤立的引用环

 

from sys import getrefcount
import objgraph


lt1 = [0, 8, 2, 4]
lt2 = lt1

# 1.查看对象 [0, 8, 2, 4]的引用计数
print(getrefcount(lt1) - 1)

# 2.查看词典(记录全局变量的引用)对象
print(globals())

# 3.查看容器对象引用的拓扑结构
x = [1, 9, 9, 5]
y = [x, dict(key1=x)]
z = [y, (x, y)]

# def show_refs(objs, max_depth=3, extra_ignore=(), filter=None, too_many=10,
#               highlight=None, filename=None, extra_info=None,
#               refcounts=False, shortnames=True, output=None)
# max_depth / too_many: 限制图形的深度和宽度
# extra_ignore / fileter:删除图标中不需要的对象
# hightlight:以蓝色突出显示某些图形结点
# filename / output:``output`` and ``filename`` should not both be specified.
# extra_info:显示对象的额外信息
# refcounts: 是否查看对象引用计数
objgraph.show_refs([z], filename="ref_topo.dot")

(三)引用减少

python内置关键字del删除的是对象的引用,而不是内存中的对象。

from sys import getrefcount


a = [1, 2, 3]
b = a
c = a
print(getrefcount(a))
del c
print(getrefcount(a))
del b
print(getrefcount(a))

(四)垃圾回收

  1. 当python某个对象引用计数为0,说明该对象无引用。python会启动“垃圾回收”,将无用的对象清除(从内存中清除)
    问题:频繁的垃圾回收,会大大降低Python的工作效率。故,python只会在特定的条件下,自动启动垃圾回收。

  2. python通过阙值( |“分配对象次数” - “取消分配对象次数”| )来判断是否进行垃圾回收。(高于阙值则进行垃圾回收)

  3. 可通过gc包的get_threshold()函数查看阙值大小;set_threshold()函数设置阙值大小。

from sys import getrefcount
import gc


a = [1, 2, 3]
b = a
c = b
print(getrefcount(a) - 1)

print(gc.get_threshold())
gc.set_threshold(300)
print(gc.get_threshold())


OUTPUT:
--> (700, 10, 10)
--> (300, 10, 10)

# ---------------------------说明-------------------------------- #
#     结果中的第一个参数代表阈值的大小
#     第二个参数代表“每10次0代垃圾回收,会配合一次1代垃圾回收”(分代回收)
#     第三个参数代表“每10次1代垃圾回收,会配合一次2代垃圾回收”(分代回收)
#     后两个参数同样是通过gc.set_threshold()函数进行修改
# -----------------后两个参数涉及到分代回收的问题------------------- #

(五)分代回收(以空间换时间进一步提高垃圾回收效率)

  1. python同时采用分代回收策略。该策略假设:存活时间越久的对象,越不可能在后边的程序当中编程垃圾。

  2. python将所有对象分为0, 1, 2三代对象。

  3. 所有的新建对象都是0代对象。当某一代对象经历过垃圾回收,依然存活,那么它就被归入下一代对象。垃圾回收启动
    时,一定会扫描所有的0代对象。如果0代经过一定次数垃圾回收,那么就启动对0代和1代的扫描清理。当1代也经历了一
    定次数的垃圾回收后,那么会启动对0,1,2,即对所有对象进行扫描

  4. 查看和修改如(四)中代码所示。

(六)孤立的引用环

lt1 = [1, 2, 3]
lt2 = [lt1]

lt1.append(lt2)

del lt1
del lt2

说明:

上边代码块中创建了两个列表对象lt1, lt2 。这两个列表对象相互引用,形成孤立的引用环。当删除lt1, lt2的时候,以上两个列表对象在程序中将无法被调用,但是其实际的引用计数并不为0,不会被垃圾回收。

为了回收这样的引用环,python复制每一个对象的引用计数(lt1:1, lt2:1)。然后,python遍历所有的引用环涉及到的对象,该处仅有lt1 和lt2 ,当遍历到lt1时,由于lt1引用了lt2, 故将lt2的引用计数减1。同理,当遍历到lt2的时候将lt1的引用计数减1,结果他们的值都为0,最后将不为0的对象保留,为0 的对象进行垃圾回收。

python3内存回收__动态类型 / 可变数据类型 / 引用计数 / 引用减少 / 垃圾回收 / 分代回收 / 孤立的引用环

但是这样就有一个问题,假设对象A有一个对象引用C,而C没有引用A,如果将C计数引用减1,而最后A并没有被回收,显然,我们错误的将C的引用计数减1,这将导致在未来的某个时刻出现一个对C的悬空引用。这就要求我们必须在A没有被删除的情况下复原C的引用计数,如果采用这样的方案,那么维护引用计数的复杂度将成倍增加。

因此,“标签-清除”方法显得更好。

(七)标签 - 清除法

首先,他先划分出两拨,一拨叫root object(存活组),一拨叫unreachable(死亡组)。然后,他把各个对象的引用计数复制出来,对这个副本进行引用环的摘除。摘除完毕,a和c的引用计数副本为0,b的引用计数副本为1,则将那么先把副本为非0的放到存活组(b),副本为0的打入死亡组(a, c)。那么此时若将引用计数为0的对象从内存中清除,则b在引用c的时候就会产生对c的悬空引用。为解决这种问题,python会在存活组中对每个对象都分析一遍,由于目前存活组只有b,那么他只对b分析,因为b要存活,所以b里的元素也要存活,于是在b中就发现了原a所指向的对象,于是就把他从死亡组中解救出来。至此,进过了一审和二审,最终把所有的任然在死亡组中的对象通通杀掉,而root object继续存活。b所指向的对象引用计数依然是2,原c所指向的对象的引用计数仍然是1。

python3内存回收__动态类型 / 可变数据类型 / 引用计数 / 引用减少 / 垃圾回收 / 分代回收 / 孤立的引用环

作者:admin_maxin 
原文:https://blog.csdn.net/admin_maxin/article/details/81632580 


python3内存回收__动态类型 / 可变数据类型 / 引用计数 / 引用减少 / 垃圾回收 / 分代回收 / 孤立的引用环

识别图中二维码,欢迎关注python宝典

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值