Python基础30-面向对象(内存管理机制-引用计数/垃圾回收
2018-05-24 本文已影响211人
Jacob_LJ
Python基础-面向对象(内存管理机制)
2.2.2.1 如何找到
1 对象存储
- 在Python中万物皆对象
不存在基本数据类型,`0, 1.2, True, False, "abc"`等,这些全都是对象
- 所有对象, 都会在内存中开辟一块空间进行存储
2.1 会根据不同的类型以及内容, 开辟不同的空间大小进行存储
2.2 返回该空间的地址给外界接收(称为"引用"), 用于后续对这个对象的操作
2.3 可通过 id() 函数获取内存地址(10进制)
2.4 通过 hex() 函数可以查看对应的16进制地址
class Person:
pass
p = Person()
print(p)
print(id(p))
print(hex(id(p)))
>>>> 打印结果
<__main__.Person object at 0x107030470>
4412605552
0x107030470
- 对于整数和短小的字符, Python会进行缓存; 不会创建多个相同对象
此时, 被多次赋值, 只会有多份引用
num1 = 2
num2 = 2
print(id(num1), id(num2))
>>>> 打印结果
4366584464 4366584464
- 容器对象, 存储的其他对象, 仅仅是其他对象的引用, 并不是其他对象本身
4.1 比如字典, 列表, 元组这些"容器对象"
4.2 全局变量是由一个大字典进行引用
4.3 可通过 global() 查看
2 对象回收
2.1 引用计数器
2.1.1概念
- 一个对象, 会记录着自身被引用的个数
- 每增加一个引用, 这个对象的引用计数会自动+1
- 每减少一个引用, 这个对象的引用计数会自动-1
2.1.2 计数器变化常见场景
- 引用计数+1场景
1、对象被创建
p1 = Person()
2、对象被引用
p2 = p1
3、对象被作为参数,传入到一个函数中
log(p1)
这里注意会+2, 因为内部有两个属性引用着这个参数
4、对象作为一个元素,存储在容器中
l = [p1]
- +1 情况3说明:内部有两个属性引用着这个参数
Python2.x 下打印 :_globals__
和func_globals
引用该参数对象,计数+2
Python3.x 下打印:则只有一个_globals__
引用该对象,同样计数+2
import sys
class Person:
pass
p_xxx = Person() # 1
print(sys.getrefcount(p_xxx))
def log(obj):
print(sys.getrefcount(obj))
log(p_xxx)
# for attr in dir(log):
# print(attr, getattr(log, attr))
>>>> 打印结果
2
4
- 引用计数-1场景
1、对象的别名被显式销毁
del p1
2、对象的别名被赋予新的对象
p1 = 123
3、一个对象离开它的作用域
一个函数执行完毕时
内部的局部变量关联的对象, 它的引用计数就会-1
4、对象所在的容器被销毁,或从容器中删除对象
2.1.3 查看引用计数
- 注意计数器会>1,因为对象在 getrefcount方法中被引用
import sys
sys.getrefcount(对象)
import sys
class Person:
pass
p1 = Person() # 1
print(sys.getrefcount(p1)) # 2
p2 = p1 # 2
print(sys.getrefcount(p1)) # 3
del p2 # 1
print(sys.getrefcount(p1)) # 2
del p1
# print(sys.getrefcount(p1)) #error,因为上一行代码执行类p1对象已经销毁
>>>> 打印结果
2
3
2
2.2 循环引用
- 对象间互相引用,导致对象不能通过引用计数器进行销毁
# 循环引用
class Person:
pass
class Dog:
pass
p = Person()
d = Dog()
p.pet = d
d.master = p
- 使用 objgraph 模块
- objgraph.count() 可以查看, 垃圾回收器, 跟踪的对象个数
# 正常情况
import objgraph
class Person:
pass
class Dog:
pass
p = Person()
d = Dog()
print(objgraph.count("Person"))
print(objgraph.count("Dog"))
# 删除 p, d之后, 对应的对象是否被释放掉
del p
del d
print(objgraph.count("Person"))
print(objgraph.count("Dog"))
>>>> 打印结果
1
1
0
0
# 循环引用
import objgraph
class Person:
pass
class Dog:
pass
p = Person()
d = Dog()
print(objgraph.count("Person"))
print(objgraph.count("Dog"))
p.pet = d
d.master = p
# 删除 p, d之后, 对应的对象是否被释放掉
del p
del d
print(objgraph.count("Person"))
print(objgraph.count("Dog"))
>>>> 打印结果
1
1
1
1
2.2 垃圾回收
2.2.1 主要作用
- 从经历过
引用计数器机制
仍未被释放的对象中, 找到循环引用
对象, 并回收相关对象
2.2.2 垃圾回收底层机制
2.2.2.1 如何找到 循环引用
对象
- 收集所有的"容器对象", 通过一个双向链表进行引用
* 容器对象
可以引用其他对象的对象
列表
元组
字典
自定义类对象
...
* 非容器对象
不能引用其他对象的对象
数值
字符串
布尔
...
注意: 针对于这些非容器对象的内存, 有其他的管理机制
- 针对于每一个"容器对象", 通过一个变量gc_refs来记录当前对应的引用计数
- 对于每个"容器对象",找到它引用的"容器对象", 并将这个"容器对象"的引用计数 -1
- 经过步骤3之后, 如果一个"容器对象"的引用计数为0, 就代表这个对象可以被回收, 而它肯定是因为"循环引用"导致不能被回收(存活到现在)
2.2.2.2 如何提升查找"循环引用"的性能?
1 问题:
- 如果程序当中创建了很多个对象, 而针对于每一个对象都要参与"检测"过程; 则会非常的耗费性能
2 假设:
- 基于这个问题, 产生了一种假设:
1. 越命大的对象, 越长寿
2. 假设一个对象10次检测都没给它干掉, 就认定这个对象一定很长寿, 就减少这货的"检测频率"
3 设计机制:
-
分待回收
机制
1. 默认一个对象被创建出来后, 属于 0 代
2. 如果经历过这一代"垃圾回收"后, 依然存活, 则划分到下一代
3. "垃圾回收"的周期顺序为
0代"垃圾回收"一定次数, 会触发 0代和1代回收
1代"垃圾回收"一定次数, 会触发0代, 1代和2代回收
2.2.2.3 垃圾检测时机
- 垃圾回收器当中, 新增的对象个数-消亡的对象个数 , 达到一定的阈值时, 才会触发, 垃圾检测
- 通过 gc 模块查看当前垃圾回收机制促发条件及设置条件
import gc
# 获取
print(gc.get_threshold())
>>>> 打印结果
(700, 10, 10)
# 参数1,700;代表:新增的对象个数-消亡的对象个数 == 700 时会促发垃圾检测时机
# 参数2,10;代表:当第0代对象检测次数达到10次时候,会促发0代和1代对象的检测
# 参数3,10;代表:当第1代对象检测次数达到10次时候,会促发0代、1代和2代对象的检测
# 设置
gc.set_threshold(200, 5, 5)
print(gc.get_threshold())
>>>> 打印结果
(200, 5, 5)
2.2.3 垃圾回收时机
2.2.3.1 自动回收
- 触发条件
* 开启垃圾回收机制
* 且达到启动垃圾回收对应的阈值
- 开启垃圾回收机制
gc.enable()
开启垃圾回收机制(默认开启)
gc.disable()
关闭垃圾回收机制
gc.isenabled()
判定是否开启
- 启动垃圾回收对应的阈值
- 垃圾回收器中, 新增的对象个数和释放的对象个数之差到达某个阈值
查看方法
gc.get_threshold()
获取自动回收阈值
gc.set_threshold()
设置自动回收阈值
# 自动回收
import gc
gc.disable()
print(gc.isenabled())
gc.enable()
print(gc.isenabled())
print(gc.get_threshold())
gc.set_threshold(1000, 15, 5)
一般会将对应的阈值设置成更大的值,这样可以提高程序性能
2.2.3.2 手动回收
- 触发条件
- 调用 gc 模块的collection() 方法
gc.collect(generation=None)
"""
collect([generation]) -> n
With no arguments, run a full collection. The optional argument
may be an integer specifying which generation to collect. A ValueError
is raised if the generation number is invalid.
The number of unreachable objects is returned.
"""
- generation 参数:如果为空时,则表明全代回收;指定代数的话,则会检测该代之前的所有对象,如:generation = 2,则检测0、1和2代的对象
- 不管之前垃圾回收机制是否开启,调用 collection() 方法后都会执行一次垃圾回收
- 通过 objgraph 查看该类实例引用计数
import objgraph
class Person:
pass
class Dog:
pass
p = Person()
d = Dog()
print(objgraph.count("Person"))
print(objgraph.count("Dog"))
>>>> 打印结果
0
0
- 因循环引用情况,引用计数器处理不了
import objgraph
class Person:
pass
class Dog:
pass
p = Person()
d = Dog()
p.pet = d
d.master = p
del p
del d
print(objgraph.count("Person"))
print(objgraph.count("Dog"))
>>>> 打印结果
1
1
- 手动促发垃圾回收,处理循环引用对象
import objgraph
import gc
class Person:
pass
class Dog:
pass
p = Person()
d = Dog()
p.pet = d
d.master = p
del p
del d
gc.collect() #手动触发垃圾回收
print(objgraph.count("Person"))
print(objgraph.count("Dog"))
>>>> 打印结果
0
0
- 即使将垃圾回收机制关闭,调用gc.collect() 时候都会启动触发垃圾回收,执行完后并未自动开启
import objgraph
import gc
gc.disable() #手动关闭垃圾回收
class Person:
pass
class Dog:
pass
p = Person()
d = Dog()
p.pet = d
d.master = p
del p
del d
gc.collect() #手动启动垃圾回收
print(objgraph.count("Person"))
print(objgraph.count("Dog"))
print(gc.isenabled()) # 检查关闭后是否自动开启
>>>> 打印结果
0
0
False
3 循环引用 - 版本兼容方案、弱引用打破循环引用
- 循环引用通过手动启动垃圾回收机制进行回收
import objgraph
import gc
# 1. 定义了两个类
class Person:
pass
class Dog:
pass
# 2. 根据这两个类, 创建出两个实例对象
p = Person()
d = Dog()
# 3. 让两个实例对象之间互相引用, 造成循环引用
p.pet = d
d.master = p
# 4. 尝试删除可到达引用之后, 测试真实对象是否有被回收
del p
del d
# 5. 通过"引用计数机制"无法回收; 需要借助"垃圾回收机制"进行回收
gc.collect()
print(objgraph.count("Person"))
print(objgraph.count("Dog"))
>>>> 打印结果
0
0
- 可以通过重写类方法
__del__
查看类实例被释放时标识
- Python3.x 环境
import objgraph
import gc
class Person:
def __del__(self):
print("Person对象, 被释放了")
pass
class Dog:
def __del__(self):
print("Dog对象, 被释放了")
pass
p = Person()
d = Dog()
p.pet = d
d.master = p
del p
del d
gc.collect()
print(objgraph.count("Person"))
print(objgraph.count("Dog"))
>>>> 打印结果
Person对象, 被释放了
Dog对象, 被释放了
0
0
- Python2.x 环境时:
如果循环引用对象任意一个类里面实现了__del__
方法,都会导致垃圾回收机制无法正常运行
并且不会执行__del__
方法
import objgraph
import gc
class Person:
def __del__(self):
print("Person对象, 被释放了")
pass
class Dog:
def __del__(self):
print("Dog对象, 被释放了")
pass
p = Person()
d = Dog()
p.pet = d
d.master = p
del p
del d
gc.collect()
print(objgraph.count("Person"))
print(objgraph.count("Dog"))
>>>> 打印结果
1
1
- Python2.x 中循环引用类都没有重写
__del__
时,则垃圾回收机制正常
import objgraph
import gc
class Person:
# def __del__(self):
# print("Person对象, 被释放了")
pass
class Dog:
# def __del__(self):
# print("Dog对象, 被释放了")
pass
p = Person()
d = Dog()
p.pet = d
d.master = p
del p
del d
gc.collect()
print(objgraph.count("Person"))
print(objgraph.count("Dog"))
>>>> 打印结果
0
0
4 打破循环引用的方法
- 手动强制将循环引用的属性置 None。
p.pet = None
Python2.x 环境
import objgraph
import gc
# 1. 定义了两个类
class Person:
def __del__(self):
print("Person对象, 被释放了")
pass
class Dog:
def __del__(self):
print("Dog对象, 被释放了")
pass
p = Person()
d = Dog()
p.pet = d
d.master = p
p.pet = None #强制置 None
del p
del d
gc.collect()
print(objgraph.count("Person"))
print(objgraph.count("Dog"))
>>>> 打印结果
Dog对象, 被释放了
Person对象, 被释放了
0
0
- 通过弱引用方式打破循环引用
Python2.x 环境
- 导入 weakref 模块
- 一对一弱引用
d.master = weakref.ref(p)
import objgraph
import gc
import weakref
# 1. 定义了两个类
class Person:
def __del__(self):
print("Person对象, 被释放了")
pass
class Dog:
def __del__(self):
print("Dog对象, 被释放了")
pass
p = Person()
d = Dog()
p.pet = d
d.master = weakref.ref(p)
del p
del d
gc.collect()
print(objgraph.count("Person"))
print(objgraph.count("Dog"))
>>>> 打印结果
Person对象, 被释放了
Dog对象, 被释放了
0
0
- 弱引用一对多
# 单个写法
p.pets = {"dog": weakref.ref(d1), "cat": weakref.ref(c1)}
或
字典形式
p.pets = weakref.WeakValueDictionary({"dog": d1, "cat": c1})
一般还有 如下方法
WeakKeyDictionary
WeakSet
···