ptyhon内存管理与垃圾回收机制

转载:https://www.cnblogs.com/geaozhang/p/7111961.html
https://blog.csdn.net/zx870121209/article/details/81363311

一、python变量

python中一切都是对象,包括数字、字符串、list等。由于python中万物皆对象,所以python的存储问题是对象的存储问题,并且对于每个对象,python会分配一块内存空间去存储它。

  • 我们常用的变量是对对象的引用,指向具体对象的内存空间,取对象的值(类似西加加的指针)。
  • 对象的类型是已知的(python解释器会自动推断),每个对象都包含一个头部信息(头部信息:类型标识符和引用计数器)。
    变量与对象的关系

变量名是没有类型的,类型属于对象(因为变量引用对象,所以类型随对象),变量引用什么类型的对象,变量就是什么类型的。

>>> x = 3
>>> type(x)
<class 'int'>
>>> x = 'Hello world'
>>> type(x)
<class 'str'>
>>> isinstance(3, int) #测试对象是否为指定类型的实例
True
>>> isinstance(x, str)
True

python采用基于值的内存管理方式,若不同变量赋给相同值,这个值在内存中只有一份,多个变量指向同一块内存地址。
在这里插入图片描述
在Python中修改变量值的操作并不是修改了变量的值,而是修改了变量指向的内存地址。

>>> x = 123
>>> y = 123
>>> id(x) #返回变量所指的内存地址
1814068096
>>> id(y)
1814068096
>>> x = 345
>>> id(x)
1820862549808
>>> y
123
>>> x is y #通过is来判断两个变量所指的对象是否相同,即比较所指地址是否一致
False
  • 对于整数和短小的字符等,python会执行缓存机制,即将这些对象进行缓存,不会为相同的对象分配多个内存空间。

    也就是说每个对象在内存中只存有一份,引用所指对象就是相同的,即使使用赋值语句,也只是创造新的引用,而不是对象本身;
    
  • Python没有缓存长字符串、列表及其他对象,可以有多个相同的对象,可以使用赋值语句创建出新的对象。

In [46]: a=1
In [47]: b=1
In [48]: print(a is b)
True
In [49]: c="good"
In [50]: d="good"
In [51]: print(c is d)
True
In [52]: e="very good"
In [53]: f="very good"
In [54]: print(e is f)
False
In [55]: g=[]
In [56]: h=[]
In [57]: print(g is h)
False
二、引用计数

在Python中,每个对象都有指向该对象的引用总数—引用计数

查看对象的引用计数:sys.getrefcount()
  1. 普通引用
In [2]: import sys

In [3]: a=[1,2,3]
In [4]: getrefcount(a)
Out[4]: 2

In [5]: b=a
In [6]: getrefcount(a)
Out[6]: 3
In [7]: getrefcount(b)
Out[7]: 3

当使用某个引用作为参数,传递给getrefcount()时,参数实际上创建了一个临时的引用。因此,getrefcount()所得到的结果,会比期望的多1。

  1. 容器对象
    容器对象,如列表、元组、字典等,存储的其他对象,仅仅是其他对象的引用,即地址,并不是这些对象本身。

在这里插入图片描述

  1. 引用计数增加:

1.对象被创建p = Person(),增加1;
2.对象被引用p1 = p,增加1;
3.对象被当作参数传入函数func(object),增加2,原因是函数中有两个属性在引用该对象;
4.对象存储到容器对象中l = [p],增加1

4、引用计数减少

1.对象的别名被销毁del p,减少1;
2.对象的别名被赋予其他对象,减少1;
3.对象离开自己的作用域,如getrefcount(对象)方法,每次用完后,其对对象的那个引用就会被销毁,减少1;
4.对象从容器对象中删除,或者容器对象被销毁,减少1

>>> from sys import getrefcount as gf
>>> a = [1, 23, 45]
>>> gf(45)
9
>>> a.remove(45)
>>> a
[1, 23]
>>> gf(45)
8
三、垃圾回收

转载: https://www.jb51.net/article/99296.htm
https://www.cnblogs.com/George1994/p/7349871.html
Python的GC模块主要运用了引用计数来跟踪和回收垃圾。在引用计数的基础上,还可以通过标记-清除解决容器对象可能产生的循环引用的问题。通过分代回收以空间换取时间进一步提高垃圾回收的效率。

1、引用计数机制

原理:当一个对象的引用被创建或者复制时,对象的引用计数加1;当一个对象的引用被销毁时,对象的引用计数减1,当对象的引用计数减少为0时,就意味着对象已经再没有被使用了,可以将其内存释放掉。
优点:引用计数有一个很大的优点,即实时性,任何内存,一旦没有指向它的引用,就会被立即回收,而其他的垃圾收集技术必须在某种特殊条件下才能进行无效内存的回收。
弱点::引用计数机制所带来的维护引用计数的额外操作与Python运行中所进行的内存分配和释放,引用赋值的次数是成正比的,这显然比其它那些垃圾收集技术所带来的额外操作只是与待回收的内存数量有关的效率要低。

同时,引用技术还存在另外一个很大的问题-循环引用,因为对象之间相互引用,每个对象的引用都不会为0,所以这些对象所占用的内存始终都不会被释放掉。如下:

class Person(object):
    pass
class Dog(object):
    pass
p = Person()
d = Dog()
p.pet = d
d.master = p

即对象p中的属性引用d,而对象d中属性同时来引用p,从而造成仅仅删除p和d对象,也无法释放其内存空间,因为他们依然在被引用。深入解释就是,循环引用后,p和d被引用个数为2,删除p和d对象后,两者被引用个数变为1,并不是0,而python只有在检查到一个对象的被引用个数为0时,才会自动释放其内存,所以这里无法释放p和d的内存空间

注意: 
1、垃圾回收时,Python不能进行其它的任务,频繁的垃圾回收将大大降低Python的工作效率;
2、Python只会在特定条件下,自动启动垃圾回收(垃圾对象少就没必要回收)
3、当Python运行时,会记录其中分配对象(object allocation)和取消分配对象(object deallocation)的次数。当两者的差值高于某个阈值时,垃圾回收才会启动。

In [93]: import gc

In [94]: gc.get_threshold()  #gc模块中查看阈值的方法
Out[94]: (700, 10, 10)

返回(700, 10, 10),后面的两个10是与分代回收相关的阈值,后面可以看到。700即是垃圾回收启动的阈值。可以通过gc中的set_threshold()方法重新设置。

也可以使用gc.collect()手动启动垃圾回收。
2、分代回收

Python同时采用了分代(generation)回收的策略。

这一策略的基本假设是,存活时间越久的对象,越不可能在后面的程序中变成垃圾。我们的程序往往会产生大量的对象,许多对象很快产生和消失,但也有一些对象长期被使用。出于信任和效率,对于这样一些“长寿”对象,我们相信它们的用处,所以减少在垃圾回收中扫描它们的频率。

Python将所有的对象分为0,1,2三代。所有的新建对象都是0代对象。当某一代对象经历过垃圾回收,依然存活,那么它就被归入下一代对象。垃圾回收启动时,一定会扫描所有的0代对象。如果0代经过一定次数垃圾回收,那么就启动对0代和1代的扫描清理。当1代也经历了一定次数的垃圾回收后,那么会启动对0,1,2,即对所有对象进行扫描。

上面get_threshold()返回的(700, 10, 10)返回的两个10。也就是说,每10次0代垃圾回收,会配合1次1代的垃圾回收;而每10次1代的垃圾回收,才会有1次的2代垃圾回收。

3、关于垃圾回收(底层层面–原理)

转载:https://www.jb51.net/article/99296.htm

垃圾回收的作用:从经过引用计数器机制后还没有被释放掉内存的对象中,找到循环引用对象,并释放掉其内存

垃圾回收检测流程:

1、收集所有容器对象(循环引用只针对于容器对象,其他对象不会产生循环引用),使用双向链表(可以看作一个集合)对这些对象进行引用;
2、针对每一个容器对象,使用变量gc_refs来记录当前对应的应用个数。 Python复制每个对象的引用计数,可以记为gc_ref。假设,每个对象i,该计数为gc_ref_i。
3、对于每个容器对象,找到其正在引用的其他容器对象,并将这个被引用的容器对象引用计数减去1。Python会遍历所有的对象i。对于每个对象i引用的对象j,将相应的gc_ref_j减1。
4、步骤3后,检查所有容器对象的引用计数,若为0,则证明该容器对象是由于循环引用存活下来的,并对其进行销毁

如何提升查找循环引用过程的性能:由上可知,循环引用查找和销毁过程非常繁琐,要分别处理每一个容器对象,所以python考虑一种改善性能的做法,即分代回收。首先是一个假设–如果一个对象被检测了10次还没有被销毁,就减少对其的检测频率;基于这个假设,提出一套机制,即分代回收机制。
在这里插入图片描述

在这里插入图片描述

四、python内存管理

转载:https://www.cnblogs.com/George1994/p/7349871.html
https://blog.csdn.net/zhzhl202/article/details/7547445
https://blog.csdn.net/u010899985/article/details/81450123

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值