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

1.动态类型对象是存储在内存中的实体。但我们并不能直接接触到该对象。引用与对象分离是动态类型的核心。(一)不可变数据类型:#?--------------------引例1a?=?1b?=?aa?=?a?+?2print(a,?b)OUTPUT:--?3??1#?-...

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对象的引用均会受到影响。

 

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 中对象引用的拓扑结构):

 

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 的对象进行垃圾回收。

但是这样就有一个问题,假设对象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。

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


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

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