python---核心知识8之对象的生命周期以及内存管理机制

生命周期的概念:世界上的万事万物都有它的生命周期,那么针对对象的生命周期到底是从哪里开始从哪里结束呢?当我们创建一个对象时,会自动分配一个内存地址给这个对象使用,这就是生命周期的开始,当我们不使用这个对象时,就会回收这个对象,并释放对象所占用的内存地址,这就是对象的消亡;

那么针对对象的生命周期我们应该如何监控呢?主要就是3个方法,首先创建对象的时候,会执行__new__方法,当对象创建成功时会调用__init__这个初始化方法,当对象被回收的时候会调用__del__方法,我们主要就是通过这3个方法来监控对象的生命周期:

class Person:

    # def __new__(cls, *args, **kwargs):

        # print('我被创建了')

    def __init__(self):

        print('创建成功了')

    def __del__(self):

        print('我被删除了')

p=Person()

结果:

创建成功了

我被删除了

生命周期小案例:

在讲生命周期小案例之前我们先补充两个小的知识点,就是global和nonlocal这两个关键字,global是修饰全局变量的,当用global修饰之后,我们在局部进行赋值操作的时候,就会把局部赋值的这个变量当成全局变量,nonlocal这个关键字是修饰函数的变量,特别需要注意的是nonlocal修饰类属性是起不到作用的,nonlocal只能作用于函数的变量,当我们用这个关键字修饰的时候,那么在内函数给变量赋值就会把这个变量当成外函数所定义的变量:

global的使用:

num=5

def text_global():

    global num

    num=6

    print(num)

text_global()

nonlocal的使用:

python---核心知识8之对象的生命周期以及内存管理机制_第1张图片

现在我们在来看看生命周期的小案例:比如我们需要统计一个班级的学生,当增加一个学生,或者减少一个学生我们就把学生数输出出来,分析:首先肯定需要一个学生类,然后当创建一个对象,或者减少一个对象我们都需要输出当前对象的个数:

下面就把代码贴出来:我们来分析一下代码,首先学生的增减时外部不能控制的,所以我们这个属性应该是私有属性,是由类本身决定的,当实例出一个对象会调用__init__方法,当回收一个对象会调用__del__方法,所以我们在这两个方法里面去操控数据以及输出,这里需要注意一点是属性的查找操作和赋值操作有不同的机制,比如说我们用实例查找属性时首先会在自身的__dict__去找,如果没有就会去__class__对应的类去查找,但是赋值操作不一样,因为赋值操作有两种含义,一种是修改值,一种是增加属性,比如说我们给实例属性一个赋值操作,如果实例有这个属性的话就是修改,如果实例没有这个属性的话就是给这个实例增加这个属性,并不是继续往它的class去找,我们需要记住赋值操作永远都是针对自己,而不会去找其他人,就好比你有一百万,你肯定会自己留着,而不会给别人一个道理,那么我们给类的属性赋值,直接用类去点,或者通过实例找到类再去操作,好了,扯远了,就酱;

python---核心知识8之对象的生命周期以及内存管理机制_第2张图片

内存管理机制:

首先我们从存储的层面去看,1.在python里面万物皆对象,不像java里面有什么基本数据类型,而python里面不存在基本数据类型,1,2,'a',等都是对象;2.所有的对象在内存里面都会开辟一块空间给它存储,解释器会根据不同的类型以及大小开辟不同的空间存储,在返回空间的存储地址以便于外界对这个对象的操作,3.对于整数和短小的字符,python会对其进行缓存,不会创建多个相同的对象,4.对于集合,列表等容器对象存储的其他对象仅仅是存储的其他对象的引用,并不是对象本身;

我们只验证一下python的缓存,其他没啥可验证的:


python---核心知识8之对象的生命周期以及内存管理机制_第3张图片

接下来我们来看看垃圾回收方面:说到垃圾回收,肯定会涉及到引用计数器的概念,所谓引用计数器就是对当前对象被引用的个数,当增加引用的时候,这个引用计数器就会自动加一,当减少一个引用的时候,这个引用计数器就会减一,举例看看:

从下面的代码来看,获取引用个数是通过sys模块的getrefcount()函数,里面直接传对象,并且需要注意的是当用getrefcount()获取引用个数的时候,会比真正的引用个数大一,因为当前也有一个正在被getrefcount用着;但是当这个函数执行完之后,那么它的引用也会被释放,下面我们再来看看什么情况会增加:1.当我们实例出对象的时候,2.当这个对象被赋值引用的时候,3.当对象被函数引用的时候,特别需要注意的是被函数引用个数是加2,4.当被容器当成元素的时候,那么什么情况又会减少呢?1.使用del删除的时候,2.函数执行完毕的时候,3.容量被删除或者容器内该元素被移除,

python---核心知识8之对象的生命周期以及内存管理机制_第4张图片

我们在来分析一下为什么对象传进函数会增加两次呢?这两次分别是哪里:通过代码可以看到person1在text函数的属性里出现过两次,分别是__globals__和func_globals,所以对象被函数调用计数器会加2


python---核心知识8之对象的生命周期以及内存管理机制_第5张图片

引用计数器循环引用的问题:比如说有这样的一个场景,有一个teacher类有一个student类,teacher类有一个属性指向student,而student也有个属性指向teacher,在这种情况下当我们分别删除实例出的对象之后,那属性指向的对象会被回收吗?我们看代码:这里我们安装了一个objgraph模块,里面有个方法count统计的是垃圾回收器跟踪的对象个数,说白了就是当前实例出来的对象的个数,从下面的代码我们可以看到当我们把s和t实例删除之后,实例对象仍然存在,这就造成了内存泄漏,那针对这种问题该怎么解决呢?其实python的内存管理机制由两部分组成:计数器机制和垃圾回收机制;

python---核心知识8之对象的生命周期以及内存管理机制_第6张图片

垃圾回收机制:其实一般情况下的垃圾通过引用计数器都能回收,垃圾回收机制是针对于通过了引用计数器还存活的对象,在找到循环引用的对象,最后在释放它;

垃圾回收机制的原理:

1.通过引用计数器还能存活的垃圾其实就是被循环引用的对象,所以我们第一步肯定是要找到所有被循环引用的对象,能被循环引用的对象其实都是容器对象,容器对象指的就是能装对象的容器,比如说字典,列表,等等,你想想像int这种对象能产生循环引用的效果吗,产生循环引用就是因为互相都有属性指向对方,在说明白一点就是互相都能装其他对象,那int能装其他对象吗,明显是不能的,所以我们第一步就是找到所有的容器对象,找到了所有的容器对象,肯定要找个东西存下来啊,并且这个东西要很灵活方便的处理里面装的东西,所以选择了双向链表,对于双向链表的优点自行百度;

2.找到了容器变量之后,自然是看这个对象有多少个引用,大家如果不好理解,其实就可以把容器对象理解成普通的没被回收的对象,底层是把引用的数字存在了变量gc_refs里面;

3.对于每个容器对象a,要找到它所引用的容器对象b,并且把容器对象b的引用数减一,这一步就是最重要的,相当于把互相引用的那条引用去掉;

4.通过了第3步,如果有容器对象的引用数为0.证明它肯定是被循环引用才活到现在的,所以就把这个对象回收掉;

垃圾回收机制的优化:比如说我们有一万个对象都是存活的,每隔5s进行一次检测,总不能每次都去检测一万个吧,这样是很耗时的,性能会降低很多,所以python使用的是分代回收,什么是分代回收呢?比如说我们总共有一百个对象,在经历了10次检测之后,有50个对象还是存活的,那么我们就把这50个对象分为一代,在进行重新检测的时候我们就不用检测这50个对象。当检测到一定次数的时候,会促发检测一次这50个对象,也就是一代,当一代经历过10次检测之后还剩5个存活对象,那就把这5个存活对象分为二代,当一代检测到一定次数就会促发第二代的检测,如此类推,其实就有点像是差生和优生之间的检测,在经过100次考试之后,有10个一直都是优生,那么这10个人接下来就可以不用进行测试,当继续测试到一定次数之后,这10个人才又参加一次测试,这是这个道理;

垃圾回收机制配置参数:我们的垃圾回收机制除了使用分代回收之外,还在是否促发检测上面给定了一个阈值,只有当目前存在的对象达到这个阈值时才会促发检测;我们通过gc模块可以得到这个阈值,也能修改这个阈值:我们可以看到下面的代码,一共有三个参数,第一个是阈值,第二个是促发一代检测的次数,第三个是促发二代检测的次数;

python---核心知识8之对象的生命周期以及内存管理机制_第7张图片

垃圾回收机制自动回收需要的条件:

1.垃圾回收机制是打开的;垃圾回收可以通过gc模块来进行打开或者关闭,也可以判断当前垃圾回收是否打开;默认情况下是打开的;

2.达到指定的阈值;


python---核心知识8之对象的生命周期以及内存管理机制_第8张图片

垃圾回收机制的手动触发:我们知道垃圾回收机制在一定的条件下会自动促发,但是这个条件往往比较高,有时候需要手动促发,手动促发用的是gc模块的collect方法,这个方法有一个选填参数,默认不填是检测0,1,2代的垃圾,填1检测的是0和1代的垃圾,填2检测的是0,1,2,的垃圾;我们可以看下面的代码通过gc.collect()把之前不能回收的循环引用的对象都回收了;

python---核心知识8之对象的生命周期以及内存管理机制_第9张图片

垃圾回收机制解决循环引用的兼容问题:如下代码所示,我们把python版本改成了2.7,并且重写了类的del方法,我们会发现gc.collect()函数不管用了,还是回收不到这两个对象,接下来我们就来解决这样的问题

python---核心知识8之对象的生命周期以及内存管理机制_第10张图片

解决方案1:通过弱引用来代替强引用,引用对象,所谓弱引用其实就是当我们使用弱引用引用一个对象时,这个对象的refcount不会增加,这样的话就不会造成循环引用,自然就解决了;我们看下面的代码,弱引用用的是weakref模块下的ref方法;在补充一点获取弱引用字典可以使用weakref.WeakKeyDictionary这个是字典的key全是弱引用以及weakref.WeakValueDictionary这个是字典的值全是弱引用;


python---核心知识8之对象的生命周期以及内存管理机制_第11张图片

解决方案2:就是我们手动把他们之一的引用给释放掉,就相当于是置空,这样也就没有了循环引用:


python---核心知识8之对象的生命周期以及内存管理机制_第12张图片

至此,这一大块就结束了,讲的不对的地方,希望大家给出指正的意见;

你可能感兴趣的:(python---核心知识8之对象的生命周期以及内存管理机制)