python课程回顾复习记录简要12

垃圾回收机制

1.内存小知识

Python 内存机制,节省内存–>小对象池、大对象池、intern 机制
(1)小对象池
在 Python 中,整数应用非常广泛(int、*long),Python 解释器为了提升运行速度而使用了小整数对象池,以此来避免为整数进行频繁定义与销毁内存空间。小整数指的是被定义范围为-5~257(即[-5, 257))的这些整数,而处于这范围内的这些整数对象是提前创建好的,且不会被垃圾回收机制以内存垃圾进行回收,同时所有处于这个范围内的整数使用的都是同一个对象(引用相同、id、内存地址值–>id())。
在这里插入图片描述
(2)大对象池
在 Python 中,被定义范围在-5~257(即[-5, 257))之外的这些整数(即(-∞,-5)&[257, +∞))称之为大整数,所有处于这个范围内的整数在使用时都会创建一个新的对象—id 值改了
在这里插入图片描述
(3).intern机制
在这里插入图片描述
若 Python 解释器会创建 4个对象的话,倘若定义 1000 个对象时,比如 a1 =“hello”…… a1000 = “hello”,则需要开辟 1000 个"hello"所占的内存空间。要真是这样,内存空间就大大浪费了。因此,Python 中有一个 intern 机制,可让"hello"只占用一个"hello"所占的内存空间,且通过引用计数(+1)去判断何时释放内存。
在这里插入图片描述
从上述结果可以看出,所有的"hello"的 id 值相同,即都是
指同一个对象。

在这里,对于上述内容进行一个简要总结,如下:
(1)处于[-5,257)范围内的小整数共用同一个对象,属于常驻内存;–一旦创建,就一直使用。
(2)处于[-5,257)范围的大整数在使用时,会重新创建一个新的对象;
(3)为单个单词的字符串(或全为字母的字符串),默认遵循 intern 机制,且会共用同一个对象;
(4)对于含有空格的字符串,是不遵循 intern 机制的,即不共用对象。
在这里插入图片描述
2.Garbage Collection(GC 垃圾回收)
在 Python 中,如何垃圾回收:引用计数法、标记清除、分代回收(隔代回收)
与 C、C++语言不同,Python 对于内存垃圾不需开发者手动去释放,而有一套属于自己的垃圾回收机制(GarbageCollection),且会自动回收垃圾(增加了开发者的负担、无法精确控制垃圾回收)--了解垃圾回收。当 Python 中创建的对象越来越多时,则会占用大量的内存空间,在某个执行时机,Python 解释器就会自行“瘦身”,并对垃圾进行回收。
Garbage Collection(GC 垃圾回收)作为现代编程语言的自动垃圾回收机制–Java(与 Python 不一样)、Python,主
要应用于:
(1)找到内存中无用的内存垃圾资源;–发现
(2)清除这些内存垃圾,同时把内存空间清理出来,并给其他新对象使用。–处理
这类垃圾回收机制彻底把开发者从内存资源管理的重担中解放出来,并有更多的时间放在业务逻辑上,当然
这并不意味着开发者就可以不去了解这类机制,毕竟多了解垃圾回收机制的知识有助于写出更加健壮的代码。
(1)引用计数
Python 语言默认采用的垃圾回收机制是引用计数法(当来了一个新的对象,则给引用+1)(Reference Counting),该算法最早由 George E. Collins 在1960 年首次提出,目前,该算法仍然被很多编程语言所采用。
引用计数法的原理是:每个对象维护一个 ob_ref 字段,并用来记录该对象当前被引用的次数(0)。每当新的引用指向该对象时,它的引用计数 ob_ref 则在原基础(创建变量)上加 1;每当该对象的引用消失(del)时计数 ob_ref则在原基础上减 1……一旦某对象的引用计数 ob_ref 为 0 时,则该对象立即被回收,且该对象所占用的内存空间将被释放。它的缺点是需要使用另外的空间来维护该引用计数 ob_ref,当然这个问题是其次的,最主要的问题是它不能解决对象的“循环引用”问题,因此,也有很多编程语言(如 Java)并没有采用该算法做来垃圾的回收机制。
在这里插入图片描述
若要查看一个对象的引用计数,则使用 sys 模块下的如下函数:
在这里插入图片描述
在这里插入图片描述
(2)标记清除
标记清除(Mark Sweep)算法是一种基于追踪回收(tracing GC)技术实现的垃圾回收算法,它分为两个阶段:第一阶段是标记阶段,GC 会把所有的活动对象打上标记;第二阶段会把那些没有标记的对象(即非活动对象)进行回收。那么 GC 又是如何判断哪些是活动对象,哪些是非活动对象的呢?
在这里,有必要了解一下,对象之间通过引用(或称之为指针)连在一起,并构成一个有向图,对象构成这个
有向图的节点,而引用关系则构成了这个有向图的边。从根对象(root object)出发,沿着有向边遍历对象,可到达的对象(reachable)标记为活动对象,不可到达的对象就是要被清除的非活动对象。根对象就是全局变量、调用栈、寄存器,基本图解如下:
在这里插入图片描述
在上图中,可把小黑圈视为全局变量,也就是把它作为 root object,从小黑圈出发,对象 1 可直达,那么它将被标记,对象 2、3 可间接到达也会被标记,而 4 和 5 不可达,那么 1、2、3 就是活动对象,4 和 5 是非活动对象并会被 GC 当做内存垃圾进行回收。
标记清除算法作为 Python 的辅助垃圾回收技术处理的主要是一些容器对象,比如 list、dict、tuple、实例对象等,而对于字符串、数值等这类不可变类型的对象是不可能造成循环引用问题的。Python 使用一个双向链表将这些容器对象组织起来。不过,这种简单粗暴的标记清除算法也有明显的缺点:清除非活动对象前,它必须顺序地扫描整个堆内存,哪怕只剩下小部分活动对象也要扫描堆内存中的所有对象。

(3)分代回收(隔代回收)
分代回收是一种以空间(内存空间)换时间的操作方式,Python 将内存根据对象的存活时间划分为不同的集合,
每个集合称为一个代,Python 将内存分为了三代,分别为年轻代(第 0 代)、中年代(第 1 代)、老年代(第 2 代),他们所对应的是 3 个链表,它们的垃圾回收频率与对象的存活时间的增大而减小。新创建的对象都会分配在年轻代,年轻代链表的总数达到上限时,Python 垃圾回收机制就会被触发,把那些可以被回收的对象回收掉,而那些不会回收的对象就会被移到中年代去,依此类推,老年代中的对象是存活时间最久的对象,甚至是存活于整个系统的生命周期内。同时,分代回收是建立在标记清除技术基础之上的,分代回收同样作为 Python 的辅助垃圾回收技术处理那些容器对象。
3.gc模块
1.垃圾回收
在 Python 中,若一个对象的引用计数次数为 0,则 Python 解释器就会回收这个对象的内存。下面讲解一个简单的案例,示例代码如下:

import sys
class GarbageClass(object):
    def __init__(self):
        print("GarbageClass创建,id值为:%s"%str(hex(id(self))))
    def __del__(self):
        print("删除对象,id值为:%s"%str(hex(id(self))))
#测试函数
def test():
    while True:
        #创建对象
        gb=GarbageClass()
        print("创建对象时的引用计数为:%d"%sys.getrefcount(gb)) #比正常次数大 1
        #删除对象
        del gb
test()

在这里插入图片描述

上述案例代码中,当 gb = GarbageClass()会创建出一个对象,并放在0x173a62b3b50 内存中,gb 变量指向这个内存,这时该内存的正常引用计数为1 (输出为2,但该值比正常次数大1),而当del gb后,gb变量不再指向0x173a62b3b50内存,所以这块内存的引用计数减 1,等于 0,所以就销毁了这个对象,然后释放内存。–死循环!
下面来简要了解导致引用计数+1 与-1 的情况,先来了解导致引用计数+1 的情况,如下:
(1)当对象被创建时,如:a=23,则引用计数+1;
(2)当对象被引用时,如:b=a,则引用计数+1;
(3)当对象被作为参数(引用)进行传递到某个函数中时,如:func(a),则引用计数+1;
(4)将对象当作一个元素并存储到某个容器中,如:lists=[a, a],则引用计数+1。
再来了解导致引用计数-1 的情况,如下:
(1)当对象的变量名被显式销毁时,如:del a,则引用计数-1;
(2)当对象的变量名被赋予新的对象时,如:a=23, a=100,则引用计数-1;
(3)当某对象超出它的作用域时,如 func()函数执行完毕后,func()函数中的局部变量(成员变量不会),则引用计数-1; --locals()
(4)当对象所在的容器(列表、字典、。。。)被销毁,或从容器中删除对象时,则引用计数-1。 --remove() del pop()
2.常用函数
gc 模块提供了一些函数给开发者设置垃圾回收的选项。在上面的内容中提到“采用引用计数法管理内存的一个缺点是循环引用”,而 gc 模块的一个主要功能就是解决循环引用的问题,下面来了解一下该模块下的一些常用函数,如下:
在这里插入图片描述
3.自动垃圾回收机制
gc 模块的自动垃圾回收机制在使用时,必须要先使用“import gc”导入 gc 模块,且 is_enable()=True 才会启动自动垃圾回收机制。这个机制的主要作用就是发现并处理不可达(未标记)的内存垃圾对象,而在处理过程中,通常满足:内存垃圾回收过程 = 内存垃圾检查(发现垃圾) + 内存垃圾回收(处理垃圾)。
在 Python 中,采用分代回收的方法。把对象分为三代,一开始,对象在创建时,放在第一代中;若在一次一代的垃圾检查中,该对象存活下来,则会被放到第二代中,同理在一次二代的垃圾检查中,该对象存活下来,则会被放到第三代中。
gc 模块里面有一个长度为 3 的列表的计数器,且可以通过 gc.get_count()获取。例如(488,3,0),其中 488 是指
距离上一次一代垃圾检查时,Python 解释器内存分配的数目减去释放内存的数目(剩余内存分配的数目),注意是内存分配,而不是引用计数的增加;3 是指距离上一次二代垃圾检查,第一代垃圾检查的次数;同理,0 是指距离上一次三代垃圾检查,第二代垃圾检查的次数。下面简要来查看一下下列案例,示例代码如下:

import gc
class Class():
    def __init__(self):
        print("id值为:%s"%str(hex(id(self))))
    def __del__(self):
        print("删除对象,id值为:%s"%str(hex(id(self))))
print(gc.get_count())
class_a=Class()
print(gc.get_count())
del class_a
print(gc.get_count())

在这里插入图片描述
4.循环引用导致的内存泄漏
->内存泄漏(数据一直添加,添加到某个时间点,2G–>存放不了)、内存溢出(图片:5-10GB–手机内存 4GB)!
Python 中的垃圾回收是以引用计数为主、分代回收为辅。引用计数的缺陷是循环引用的问题。对于垃圾回收机制的应用,通常有以下几种:
(1)在实际项目中,尽量避免循环引用;–变量 1 调用 2、2 调用 1
(2)导入 gc 模块,最好启动 gc 模块的自动清理循环引用的对象机制;
(3)由于分代收集,所以把需要长期使用的变量集中管理,并尽快移到第二代以后,以减少 GC 检查时的消耗;
(4)gc 模块唯一处理不了的是循环引用的类中都有__del__()方法(继承:重写、重写后的__del__),因此项目中要避免定义__del__()方法,若一定要使用该方法,同时导致了循环引用,需要代码显式调用 gc.garbage 里面的对
象的__del__()来打破僵局。
下面来演示循环引用导致的内存泄漏的一个案例,示例代码如下:

import gc
class MyClass:
    def __init__(self):
        print("创建对象时id值为:%s"%str(hex(id(self))))
#测试函数
def test():
    while 1:
        mc1=MyClass()
        mc2=MyClass()
        #给对象1添加属性
        mc1.val=mc2
        #给对象2添加属性
        mc2.val=mc1
        #删除对象
        del mc1
        del mc2
#调用
test()

在这里插入图片描述
在 del mc1 后,内存 A 的对象的引用计数变为 1,而不是为 0,因此内存 A 的对象不会被销毁;内存 B 的对象的引用计数依然是 2,在 del mc2 后,同理,内存 A 的对象、内存 B 的对象的引用数都是 1。虽然内存 A 与内存 B 这两个对象都是可以被销毁的,但由于循环引用,导致垃圾回收器都不会回收它们,持续循环运行,最终就会导致内存泄露,使用 Ctrl+Alt+Del 快捷键打开任务管理器,查看 Python 解释器所占用内存大小如下:(自动回收垃圾)–占用内存提升不少很快!
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
关闭自动回收垃圾:占用内存提升飞快。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值