一、关于Python存储的问题
1.Python中万物皆对象,所以Python的存储问题就是对象的存储问题,每一个对象Python都会分配一块内存进行存储。
2.存储多个相同的整数和浮点类型等较小的字符类型时,Python会执行缓存机制,不会同时存储多个相同的对象。
3.列表、元组、字典等复杂数据类型存储其他对象时,存储的是对象的内存地址,而不是对象本身。
二、Python的三种内存管理机制
1.引用计数机制
2.垃圾回收机制
3.内存池机制
三、引用计数机制
定义:使用引用计数器方法,当对象的引用数为0时,释放该对象内存空间的机制。
(1)引用计数器方法:对象每多一个引用,计数加一;每减少一个引用,计数减一。
(2)引用增加的条件:①对象被创建时,计数+1 例:p = person()
②对象被引用时,计数+1 例:p1 = p
③对象作为函数的参数时,计数+1 例:person(p)
④对象作为容器数据类型的元素时,计数+1 例:b=[p]
(3)引用减少的条件:①对象的引用被删除时,计数-1 例:del p1
②对象的引用被赋予其他对象时,计数-1 例:p1 = 1
③对象作为参数的函数执行完毕后,计数-1
④对象作为容器数据类型的元素被删除,或容器被删除时,计数-1
(4)查看引用对象个数的方法:导入sys模块,使用模块中的getrefcount(对象)方法,由于这里也是一个引用,故输出的结果多1
from sys import getrefcount
a = [1, 2, 3]
print(getrefcount(a)) # 2
b = a
print(getrefcount(b)) # 3
四、垃圾回收机制
引用计数机制是一种最直观、最简单的垃圾回收机制,当引用计数为0时会被自动释放内存。但当两个容器对象之间互相引用,而且没有其他外部引用时,两个对象的引用计数都为1,这种引用称为循环引用。循环引用不会被引用计数机制自动清除,需要启动新的垃圾回收机制来处理循环引用。
循环引用示例:
a = []
b = []
a.append(b)
b.append(a)
print(a) # [[[…]]]
print(b) # [[[…]]]
找到循环引用并释放内存:
1.收集所有容器对象(循环引用只针对于容器对象,其他对象不会产生循环引用),使用双向链表(可以看作一个集合)对这些对象进行引用;
2.针对每一个容器对象,使用变量gc_refs来记录当前对应的应用个数;
3.对于每个容器对象,找到其正在引用的其他容器对象,并将这个被引用的容器对象引用计数减去1;
4.经过步骤3后,检查所有容器对象的引用计数,若为0,则证明该容器对象是由于循环引用存活下来的,并对其进行销毁
分代回收机制:由于上述释放循环引用内存的方法非常繁琐,需要逐个检查每一个容器对象。为了提高检查清除循环引用垃圾的效率,引用分代回收机制。假设将所有内存空间划分为集合A和集合B,所有新分配的内存都划分进集合A,如果某些循环引用经过n次清除依然存活,则将这些循环引用划分进集合B。在下一次清洗中主要对集合A中的对象进行清洗,而隔一段时间后才对集合B中的对象进行清洗,这个过程中集合A中经过多次清洗的对象会转移进集合B。这样每一次清洗过程中需要检查的对象少了,自然效率就会提高。当然在集合B中也会存在一些循环引用,这些循环引用的清除会由于分代回收机制而滞后。
垃圾回收的时机:分为自动启动和手动启动。
自动启动:当新增的对象个数与释放对象的个数差达到某一阈值时,会自动启动垃圾回收机制。
手动启动:使用gc模块中的collect()方法,使得执行这个方法时执行分代回收机制
import objgraph
import gc
import sys
class Person(object):
pass
class Dog(object):
pass
p = Person()
d = Dog()
p.pet = d
d.master = p
del p
del d
gc.collect()
print(objgraph.count("Person"))
print(objgraph.count("Dog"))
其中objgraph模块的count()方法是记录当前类产生的实例对象的个数