赋值语句内存分析

◆ 使用id()方法访问内存地址

◆ 使用is比较内存引用地址是否相等

在python之中,像数值、字符串、布尔型单纯的赋值,它们只要值相等,那么对应的变量名所在的地址也是相等的,而这就是python对内存管理的优化。相对的,如:[]列表的复制,其所对应的地址却是不同的。

代码:

def extend_list(val, l=[]):
    print('---------------------')
    print(l, id(l))
    l.append(val)
    print(l, id(l))
    return l


list1 = extend_list(10)
list2 = extend_list(123, [])
list3 = extend_list('a')

print(list1)
print(list2)
print(list3)

输出如下:

---------------------
[] 1976185016904
[10] 1976185016904
---------------------
[] 1976215339528
[123] 1976215339528
---------------------
[10] 1976185016904
[10, 'a'] 1976185016904
[10, 'a']
[123]
[10, 'a']

结论:对比list1、list2、list3的输出结果后,就能了解在函数中默认的列表,其在python的内存存储机制。list2不同的原因,就在于它使用的是新生成的列表,而list1和list3相同的原因,则是它们使用的是函数中默认的列表,在内存中,它们是同一个。

垃圾回收机制

◆ 以引用计数为主,分代收集为辅

◆ 如果一个对象的引用数为0,Python虚拟机就会回收这个对象的内存

◆ 引用计数的缺陷是循环引用的问题

代码(1):

class Cat(object):
    def __init__(self):
        print('对象产生了:{0}'.format(id(self)))

    def __del__(self):
        print('对象删除了:{0}'.format(id(self)))

def f0():
    while True:
        c1 = Cat()

if __name__ == '__main__':
    f0()

输出如下:

对象产生了:1775928937992
对象删除了:1775928938312
对象产生了:1775928938312
对象删除了:1775928937992
对象产生了:1775928937992
对象删除了:1775928938312
对象产生了:1775928938312
对象删除了:1775928937992

代码(2):

class Cat(object):
    def __init__(self):
        print('对象产生了:{0}'.format(id(self)))

    def __del__(self):
        print('对象删除了:{0}'.format(id(self)))


def f1():
    l = []
    while True:
        c1 = Cat()
        l.append(c1)
        print('列表元素:', len(l), '个')

if __name__ == '__main__':
    f1()

输出如下:

对象产生了:1627687778248
列表元素: 88839 个
对象产生了:1627687778312
列表元素: 88840 个
对象产生了:1627687778376
列表元素: 88841 个
对象产生了:1627687778440
列表元素: 88842 个
对象产生了:1627687778504
列表元素: 88843 个

结论:对上面两个代码和输出结果对比,能发现。第一个代码因为后续没有再对之前的生成进行使用,所以虽然不是立刻删除,但也是会马上的进行删除,这样,内存的使用空间不会一直的增长。相对的,第二个代码,它把生成的对象存放到了列表之中,这个列表对之前生成的所有对象进行了引用,所以其存在就不会被删除,内存空间早晚会溢出。

引用计数(reference count)

◆ 每个对象都存有指向该对象的引用总数

◆ 查看某个对象的引用计数

sys.getrefcount()

◆ 可以使用del关键词删除某个引用

代码:

import sys

i = 1

s = []
l2 = s
l3 = s

l5 = l3
print(sys.getrefcount(s))
del l2

#对象l被引用的数量
print(sys.getrefcount(s))

print('------------------')
print(sys.getrefcount(i))
c = i
print(sys.getrefcount(i))

输出如下:

5
4
------------------
96
97

结论:可以很明显的看出,引用数不一定是从1开始,这涉及到python当中的内存共享的机制。而每一次的直接引用或者间接引用都会使引用数增加,而对s的引用次数的观察,可发现多了1,这是因为当我们输出的时候,也会对其进行引用,所以引用次数会多一次。

垃圾回收

◆ 满足特定条件,自动启动垃圾回收

◆ 当Python运行时,会记录其中分配对象(object allocation)和取消分配对象(object deallocation)的次数

◆ 当两者的差值高于某个阈(yù)值时,垃圾回收才会启动

◆ 查看阈值gc.get_threshold()

gc — Garbage Collector interface

代码:

import gc

print(gc.get_threshold())

输出如下:

(700, 10, 10)

分代回收

◆ Python将所有的对象分为0,1,2三代

◆ 所有的新建对象都是0代对象

◆ 当某一代对象经历过垃圾回收,依然存活,那么它就被归入下一代对象

PS:即初始为0代,每次回收不掉,就对其进行代数的提升,最高为2代。而其有阈值存在,当0代回收次数到达一定的阈值,才会回收1代,而1代回收到一定的阈值,才会回收2代。而1代和2代的回收,会把前面的回收再执行。

手动回收

◆ gc.collect()手动回收

◆ objgraph模块中的count()记录当前类产生的实例对象的个数

代码(1):

import gc, sys
import objgraph

print(gc.get_threshold())

class Persion(object):
    pass

class Cat(object):
    pass

p = Persion()
c = Cat()
p.name = 'Susan'
p.pet = c

c.master = p
print(sys.getrefcount(p))
print(sys.getrefcount(c))

del p
del c

# 手动执行垃圾回收
gc.collect()
print(objgraph.count('Persion'))
print(objgraph.count('Cat'))

输出如下:

(700, 10, 10)
3
3
0
0

代码(2):

# 上面代码注释掉这两行代码
# del p
# del c

输出如下:

(700, 10, 10)
3
3
1
1

结论:如果不进行手动回收,那么就会导致回收不掉的。

内存管理机制

◆ 内存池(memory pool)机制

当创建大量消耗小内存的对象时,频繁调用new/malloc会导致大量的内存碎片,致使效率降低。内存池的概念就是预先再内存中申请一定数量的,大小相等的内存块留作备用,当有新的内存需求时,就先从内存池中分配内存给这个需求,不够了之后再申请新的内存。这样做最显著的优势就是能够减少内存碎片,提升效率。

◆ Python3中的内存管理机制——Pymalloc

针对小对象(<=512bytes),pymalloc会在内存池中申请内存空间

当>512bytes,则会PyMem_RawMalloc()和PyMem_RawRealloc()来申请新的内存空间

◆ 单位换算
1 Byte = 8 Bit (即 1B = 8b)

1 KB = 1024 Bytes

1 MB = 1024 KB

1 GB = 1024 MB

1 TB = 1024 GB

1 PB = 1024 TB

1 EB = 1024 PB

1 ZB = 1024 EB

1 YB = 1024 ZB

PS:Bit意为"位"或"比特",时计算机运算的基础,属于二进制的范畴;Byte意为"字节",时计算机文件大小的基础计算单位。

结论

慕课网 时间_记录昨天 :

从三方面来说:引用计数、垃圾回收机制、内存池机制。可以根据一下描述进行理解

一、对象的引用计数机制

Python内部使用引用计数,来保持追踪内存中的对象,所有对象都有引用计数。

引用计数增加的情况:

1、一个对象分配一个新名称

2、将其放入一个容器中(如列表、元组或字典)

引用计数减少的情况:

1、使用del语句对对象别名显示的销毁

2、引用超出作用域或被重新赋值

sys.getrefcount( )函数可以获得对象的当前引用计数

多数情况下,引用计数比你猜测得要大得多。对于不可变数据(如数字和字符串),解释器会在程序的不同部分共享内存,以便节约内存。

二、垃圾回收

1、当一个对象的引用计数归零时,它将被垃圾收集机制处理掉。

2、当两个对象a和b相互引用时,del语句可以减少a和b的引用计数,并销毁用于引用底层对象的名称。然而由于每个对象都包含一个对其他对象的应用,因此引用计数不会归零,对象也不会销毁。(从而导致内存泄露)。为解决这一问题,解释器会定期执行一个循环检测器,搜索不可访问对象的循环并删除它们。

三、内存池机制

1、Python提供了对内存的垃圾收集机制,但是它将不用的内存放到内存池而不是返回给操作系统。

2、Pymalloc机制。为了加速Python的执行效率,Python引入了一个内存池机制,用于管理对小块内存的申请和释放。

3、Python中所有小于256个字节的对象都是用pymalloc实现的分配器,而大的对象则使用系统的malloc,另外Python对象比如整数浮点数和list都有独立的私有内存池,对象间不共享他们的内存池,也就是说你分配又释放了大量的整数,用于缓存这些整数的内存就不能再分配给浮点数。