内存剖析
占用内存
int 1 4字节
float b=1.0 4字节
char c='_' 1字节
1024字节=1KB
堆和栈
C语言中,内存分配方法有2种:
- 第一种:int a = 1; char s[10] = “hello”; 在栈区申请内存
- 第二种:malloc()在堆区申请内存, free()释放内存
Python语言中:所有的对象都在堆区申请内存。
Python的内存管理器负责申请、释放内存。
引用计数
每个对象都存有指向该对象的引用总数,即引用计数(reference count)。
可以使用内置函数getrefcount()查看该对象的引用计数。
注:每使用一次getrefcount(),当前对象就又被引用一次,当前对象引用计数都会加一。
from sys import getrefcount
a = [1, 2, 3] # 这里引用计数加一
print(getrefcount(a)) # 这里引用计数再加一,输出2
垃圾回收
当Python的某个对象的引用计数为0时,说明没有任何引用指向该对象,该对象就成为要被回收的垃圾了。比如某个新建对象,它被分配给某个引用,对象的引用计数变为1。如果引用被删除,对象的引用计数为0,那么该对象就会被垃圾回收。
+1的情况:
- 赋值 、引用 x = obj obj = MyClass()
- 被其他对象使用 objects = [] objects.append(obj)
- 传给函数
-1的情况:
- 改变引用对象: x = obj x = 1
- 删除 del x
- 函数调用结束
由于垃圾回收会降低python的工作效率,所以Python只会在特定条件下,自动启动垃圾回收。当Python运行时,会记录其中分配对象和取消分配对象的次数。当两者的差值高于某个阈值时,垃圾回收才会启动。
使用get_threshold()方法查看阈值,python默认是(700, 10, 10),可以使用set_threshold()方法重新设置。
import gc
print(gc.get_threshold()) # (700, 10, 10)
上面的(700, 10, 10)这几个值是什么意思呢,这就要说到分代回收了。
分代回收
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代垃圾回收。
有一个问题:b=[1,2],a=[1,b],b.append(a),像这样的循环引用会造成内存泄漏,删除变量后他们还会互相引用,这里就用到了标记-清除机制。
标记-清除
把所有对象找出来,先检测被全局变量引用的标黑,在被全局变量间接引用的标黑,依次类推,直到没有被全局变量引用会被查出,即使有计数(如循环引用)也会被删除
总结
python的垃圾回收是引用计数机制为主,标记-清除和分代收集两种机制为辅的策略。
内存池机制
小整数池
在python中,所有变量赋值整数-5到256之间的数据相同的值id都是相同的 ,-5到256以外即使相同的值id也不同
字符串驻留池:intern机制
解释器会专门分配一块专门放纯单词字符组成的字符串(数字、字母、下划线)
a=‘123_a’
b=‘123_a’
a和b的id是相同的