内存管理
面试问题:python的内存管理机制
以引用计数为主,分代回收、标记清除为辅的垃圾回收机制
以及对小整形进行缓存和简单字符串驻留的内存池机制
接下来详细介绍Python的内存管理机制
一、引用计数
引用计数
python中的每个对象都维护一个引用计数 ob_ref字段
当有新的引用指向该对象的时候,引用计数+1
当有无效的引用的发生的时候,引用计数-1
最后引用计数为0的时候,销毁对象,该对象可以被回收,对象占用的内存空间将被释放优点
简单 实时性
缺点:
它的缺点是需要额外的空间维护引用计数,这个问题是其次的
最主要的问题是它不能解决对象的“循环引用”
1.1 循环引用
根据引用计数的规律,出现循环引用的情况,内存是无法通过引用计数来释放
这种情况就会造成内存泄漏
内存泄漏: 有一部分内存被占用无法释放,进程无又法访问(占着茅坑不拉屎)
内存溢出(oom – out of memory):内存不够。程序需要的内存大于系统的空闲内存getrefcount
当使用某个引用作为参数,传递给getrefcount()时,参数实际上创建了一个临时的引用。因此,getrefcount()所得到的结果,会比期望的多1。
>>> x = [1]
>>> y = [2]
>>> x.append(y)
>>> y.append(x)
>>> getrefcount(x)
3
# 实际的引用计数比getrefcount得到的值小1,实际的引用计数为 2
>>> getrefcount(y)
3
>>> del x
>>> del y
>>> getrefcount(x)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'x' is not defined
二、垃圾回收机制
2.1 python采用的垃圾回收机制:
分代回收: 启动垃圾回收的时候确定回收哪些对象的
标记清除: 主要解决循环引用
三种情况触发垃圾回收
- 调用gc.collect()
- GC达到阀值时
- 程序退出时
2.2 内存池
2.2.1 小整形的缓存池
# 预先创建好了一个小整形池[-5,256]
>>> a = 1
>>> getrefcount(a)
801
>>> a = 3
>>> getrefcount(a)
48
>>> a = 300
>>> getrefcount(a)
2
# 测试边界
>>> a = 256
>>> getrefcount(a)
3
>>> a = 257
>>> getrefcount(a)
2
>>> a = -6
>>> getrefcount(a)
2
>>> a = -5
>>> getrefcount(a)
3
2.2.2 字符串驻留区
单个字符创建之后会存放在字符串驻留区
多个字符,创建之后如果没有特殊字符,就会存放在字符串驻留区
# 多个字符
>>> str1 = 'abcxyz'
>>> getrefcount(str1)
2
>>> str2 = 'abcxyz'
>>> getrefcount(str2)
3
>>> id(str1)
140181150973544
>>> id(str2)
140181150973544
# 多个字符有特殊字符
>>> str1 = 'abc xyz'
>>> str2 = 'abc xyz'
>>> id(str1)
140181150486688
>>> id(str2)
140181150486744
# 多个字符没有特殊字符
>>> str1 = 'abc123'
>>> str2 = 'abc123'
>>> id(str1)
140181150973544
>>> id(str2)
140181150973544
# 单个字符
>>> str1 = '#'
>>> str2 = '#'
>>> id(str1)
140181152088560
>>> id(str2)
140181152088560
深拷贝与浅拷贝
数字和字符串、元组,不能改变对象本身,只能改变引用的指向,称为不可变数据对象(immutable object)。
列表、字典、集合可以通过引用其元素,改变对象自身(in-place change)。这种对象类型,称为可变数据对象(mutable object)
深拷贝,浅拷贝 只会发生在容器类型里面包含其他可变容器类型的情况
面试题:深拷贝和浅拷贝的区别?
浅拷贝:是将对象的引用复制给另一个对象,因此如果我们在副本中进行修改时会影响原对象。
深拷贝:是将对象本身复制给另一个对象,这意味着如果对对象的副本进行更改时不会影响原对象。
扩展:只有一种场景有深浅拷贝的区别: 容器类型里面包含其他可变容器类型的情况。
浅拷贝可能会造成修改拷贝之后的值,会改变原来的值——浅拷贝是拷贝的容器里面第一层数据的地址
深拷贝不会发生这个情况,使用copy模块的deepcopy()函数才是深拷贝——深拷贝是拷贝容器里面每一层数据
1.浅拷贝
浅拷贝只会拷贝第一层的地址
浅拷贝当改变原来的值的时候,可能会造成修改拷贝之后的值
除了copy.deepcopy是深拷贝,其他都是浅拷贝
>>> a = {"name":'sc',"score":[80,90,100]}
>>> b = a.copy()
>>> a
{'name': 'sc', 'score': [80, 90, 100]}
>>> b
{'name': 'sc', 'score': [80, 90, 100]}
>>> b["score"].append(110)
>>> b
{'name': 'sc', 'score': [80, 90, 100, 110]}
>>> a
{'name': 'sc', 'score': [80, 90, 100, 110]}
# 除了copy.deepcopy是深拷贝,其他都是浅拷贝
>>> lst = [[]]*3
>>> lst
[[], [], []]
>>> lst[0].append(1)
>>> lst
[[1], [1], [1]]
>>> id(lst[0])
140181150989000
>>> id(lst[1])
140181150989000
>>> id(lst[2])
140181150989000
2.深拷贝
深拷贝不会发生改变拷贝之后改变值的情况
深拷贝就是拷贝每一层数据
使用copy模块的deepcopy函数进行的拷贝就是深拷贝
>>> import copy
>>> b = copy.deepcopy(a) #进行深拷贝
>>> a
{'name': 'sc', 'score': [80, 90, 100, 110]}
>>> b
{'name': 'sc', 'score': [80, 90, 100, 110]}
>>> id(a)
140181150994008
>>> id(b)
140181150993504
>>> id(a['score'])
140181150989000
>>> id(b['score'])
140181018248520
>>> b['score'].append(120)
>>> b
{'name': 'sc', 'score': [80, 90, 100, 110, 120]}
>>> a
{'name': 'sc', 'score': [80, 90, 100, 110]}
练习
mylist = []
for i in range(3):
mylist.append([0,0])
print(mylist)
mylist[0][0] = 1
print(mylist)
# [[0, 0], [0, 0], [0, 0]]
# [[1, 0], [0, 0], [0, 0]]
mylist = [[0]*2]*3
print(mylist)
mylist[0][0] = 1
print(mylist)
# [[0, 0], [0, 0], [0, 0]]
# [[1, 0], [1, 0], [1, 0]]
# 两者是初始化列表是不一样的