Python基础30-面向对象(内存管理机制-引用计数/垃圾回收/循环引用/弱引用)

Python基础-面向对象(内存管理机制)

1 对象存储

  1. 在Python中万物皆对象
不存在基本数据类型,`0,  1.2,  True, False, "abc"`等,这些全都是对象
  1. 所有对象, 都会在内存中开辟一块空间进行存储
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
  1. 对于整数和短小的字符, Python会进行缓存; 不会创建多个相同对象
此时, 被多次赋值, 只会有多份引用
num1 = 2
num2 = 2
print(id(num1), id(num2))

>>>> 打印结果

4366584464 4366584464
  1. 容器对象, 存储的其他对象, 仅仅是其他对象的引用, 并不是其他对象本身
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 如何找到 循环引用 对象

  1. 收集所有的"容器对象", 通过一个双向链表进行引用
* 容器对象
    可以引用其他对象的对象
        列表
        元组
        字典
        自定义类对象
        ...
* 非容器对象
    不能引用其他对象的对象
        数值
        字符串
        布尔
        ...
    注意: 针对于这些非容器对象的内存, 有其他的管理机制
  1. 针对于每一个"容器对象", 通过一个变量gc_refs来记录当前对应的引用计数
  2. 对于每个"容器对象",找到它引用的"容器对象", 并将这个"容器对象"的引用计数 -1
  3. 经过步骤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 自动回收

  1. 触发条件
* 开启垃圾回收机制
* 且达到启动垃圾回收对应的阈值
  1. 开启垃圾回收机制
gc.enable()
    开启垃圾回收机制(默认开启)
gc.disable()
    关闭垃圾回收机制
gc.isenabled()
    判定是否开启
  1. 启动垃圾回收对应的阈值
  • 垃圾回收器中, 新增的对象个数和释放的对象个数之差到达某个阈值
查看方法
    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 手动回收

  1. 触发条件
  • 调用 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() 方法后都会执行一次垃圾回收
  1. 通过 objgraph 查看该类实例引用计数
import objgraph
class Person:
    pass

class Dog:
    pass

p = Person()
d = Dog()

print(objgraph.count("Person"))
print(objgraph.count("Dog"))

>>>> 打印结果
0
0
  1. 因循环引用情况,引用计数器处理不了
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
  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
  1. 即使将垃圾回收机制关闭,调用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 循环引用 - 版本兼容方案、弱引用打破循环引用

  1. 循环引用通过手动启动垃圾回收机制进行回收
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
  1. 可以通过重写类方法 __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 打破循环引用的方法

  1. 手动强制将循环引用的属性置 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

  1. 通过弱引用方式打破循环引用
    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
···

你可能感兴趣的:(Python基础30-面向对象(内存管理机制-引用计数/垃圾回收/循环引用/弱引用))