赋值语句内存分析
使用id()方法可以查询变量内存地址。小数值变量会采用相同的地址保存,大数值变量会采用不同的地址保存,如下图。
以下例子有助于理解内存管理:
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("+++++++++++++++++++++++++++")
print(list1)
print(list2)
print(list3)
输出为:
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[] 4828328
[10] 4828328
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[] 6660680
[123] 6660680
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[10] 4828328
[10, ‘a’] 4828328
+++++++++++++++++++++++++++
[10, ‘a’]
[123]
[10, ‘a’]
垃圾回收机制
example 1
import time
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()
time.sleep(0.5)
def f1():
""" 一直在被引用,不会被回收 """
l = []
while True:
c1 = Cat()
l.append(c1)
print(len(l))
if __name__ == '__main__':
f0()
输出为:
对象产生了: 5502624
对象产生了: 7642104
对象删除了: 5502624
对象产生了: 5502624
对象删除了: 7642104
对象产生了: 7642104
对象删除了: 5502624
对象产生了: 5502624
对象删除了: 7642104
对象产生了: 7642104
对象删除了: 5502624
…
上述程序内存占用如下:
内存消耗比较缓和,没有不断增大。由于类Cat的对象创建后没有被引用,垃圾回收机制及时回收没有引用的对象,故程序运行内存占用并没有变大。
example 2
import time
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()
time.sleep(0.5)
def f1():
""" 一直在被引用,不会被回收 """
l = []
while True:
c1 = Cat()
l.append(c1)
print(len(l))
time.sleep(0.5)
if __name__ == '__main__':
f1()
输出为:
对象产生了: 6420128
1
对象产生了: 35167152
2
对象产生了: 35167008
3
对象产生了: 35166864
4
对象产生了: 35333576
5
对象产生了: 35333120
6
对象产生了: 35332880
7
对象产生了: 35332544
8
对象产生了: 35334056
9
对象产生了: 35335280
10
对象产生了: 35335232
11
…
上述程序内存占用如下:
内存占用不断变大,导致程序一直。这是由于所建对象一直被列表l引用,故垃圾回收机制无法回收对象,导致内存占用不断变大。终止程序后列表引用的对象才逐个被删除并释放内存占用。
垃圾内存回收机制
getrefcount函数可以查询某个对象的引用计数,但是getrefcount会引入一次对该对象的临时引用,getrefcount函数调用结束这个临时引用会自动删除。
del关键字是删除对象的某个引用,而不是删除对象。
import sys
print(sys.getrefcount(5645465456))
a = 5645465456
print(sys.getrefcount(5645465456))
b = a
c = b
print(sys.getrefcount(5645465456))
print(sys.getrefcount(a)) # 6
print(sys.getrefcount(b)) # 6
输出为:
3
4
6
6
6
解释:
第一次getrefcount输出引用次数是3,调用完成后,getrefcount便不再引用,则5645465456的引用是3-1=2;
a = 5645465456,引用加1,2+1=3为3;
第二次调用getrefcount输出引用次数是3+1=4,调用完成后,getrefcount便不在引用,则5645465456的引用是4-1=3;
依次类推,最后三行输出是6,6,6。
example 3
import gc, sys
import objgraph
print(gc.get_threshold())
class Persion(object):
pass
class Cat(object):
pass
p = Persion() # 创建Person类对象,并用变量p引用
c = Cat() # 创建Cat类对象,并用变量c引用
p.name = 'Susan'
p.pet = c
c.master = p
print(sys.getrefcount(p))
print(sys.getrefcount(c))
del p # 删除p对Person类对象的引用
del c # 删除c对Cat类对象的引用
# 手动执行垃圾回收
gc.collect() # 回收p和c引用的Persion类对象和Cat类对象
print(objgraph.count('Persion'))
print(objgraph.count('Cat'))
输出为:
(700, 10, 10)
3
3
0
0
内存管理机制
python内存管理机制的理解
一、对象的引用计数机制
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都有独立的私有内存池,对象间不共享他们的内存池,也就是说你分配又释放了大量的整数,用于缓存这些整数的内存就不能再分配给浮点数。