(十二)python的内存管理机制

a=10

python中创建的对象的时候,首先会去申请内存地址,然后对对象进行初始化,所有对象都会维护在一个叫做refchain的双向循环链表中,每个数据都保存如下信息

  • 链表中数据前后数据的指针
  • 数据的类型
  • 数据值
  • 数据的引用计数
  • 数据的长度(dict,list...)

一、引用计数机制

a=1, b=a,那这个时候引用计数就是2。

但是如果li=[1] ,li2 = li,sys.getrefcount(li),那这个时候[1]这个列表的引用计数机制就是3,为什么,因为第三次他是被当成参数传到一个函数里面,所以这个时候他的引用计数就是3

(1)引用计数增加

  •  对象被创建
  • 对象被别的变量引用(另外起了个名字)
  • 对象被作为元素,放在容器中(比如被当做元素放在列表中)
  • 对象被当成参数传递到函数中

(2)引用计数减少

  • 对象的别名被显式的销毁

  • 对象的一个别名被赋值给其他对象(例:比如原来a=10,被改成a=100,那么此时10的引用计数就减少了) 

  • 对象从容器中被移除,或者容器被销毁(例:对象从列表中被移除,或者列表被销毁)

  • 一个引用离开了它的作用域(调用函数的时候传进去的参数,在函数运行结束后,该函数的引用即被销毁)

demo1:对象的别名被显式的销毁

demo2:对象的一个别名被赋值给其他对象(例:比如原来a=10,被改成a=100,那么此时10的引用计数就减少了) 

demo3:对象从容器中被移除,或者容器被销毁(例:对象从列表中被移除,或者列表被销毁)

a=[11,22]

b = [a,33]

b.pop(a)

(3)查看引用计数

import sys
sys.getrefcount(obj)

二、数据池和缓存

(1)小整数池

为什么a b c都指向的是999,但是内存地址确是不一样的?

为什么aa bb cc都指向的是1,那他们的内存地址却是一样的呢?

答: Python自动将-5~256的整数进行了缓存到一个小整数池中,当你将这些整数赋值给变量的时候,并不会重新创建对象,而是使用已经创建好的缓存对象,当删除这些数据的引用时,也不会进行回收。

 

(2)intern机制

intern机制, 也称为字符串驻留池,是针对于字符串内存管理的一种优化处理的机制。

 

intern机制的优点是,在创建新的字符串对象时,会先在缓存池里面找是否有已经存在的值相同的对象(标识符,即只包含数字、字母、下划线的字符串),如果有,则直接拿过来用(引用),避免频繁的创建和销毁内存,提升效率。 

 '123'为什么没有被销毁呢?因为他被加到字符串驻留池里面了。

 

(3)缓存机制

  •  float、int、list等一些内置的数据类型
  • 元祖 会根据元祖数据的长度,分别缓存元祖长度为0-20的对象。
  • 其他的类型缓存2个对象

为什么他们的缓存地址是一样的呢?

答:因为当你创建d1 d2的时候,他会去看你这个对象有没有缓存,没有的话就把这2个对象缓存起来,然后下一次你又重新创建了2个对象,他会先去判断你这2个对象有没有缓存,如果有缓存的话,就会去缓存里把原来对象拿出来,再把你的数据传进去,进行一个重新初始化。

三、深浅拷贝

 

两个相同的列表,他们会不会是同一个对象?

深浅拷贝:只有在数据嵌套的情况下,才需要去考虑的问题

(1)没有数据嵌套的情况下不需要考虑深浅拷贝的问题

#  没有数据嵌套的情况下不需要考虑深浅拷贝的问题
li = [11, 22, 33]
li2 = li.copy()

print(id(li))
print(id(li2))


# 打印结果
2813111183040
2813111182656

那这个时候我们往li里添加一个元素 44,对li2会有影响吗我们试一下。不会有影响。

li = [11, 22, 33]
li2 = li.copy()

print(id(li))
print(id(li2))

li.append(111)

print('li:', li)
print('li2:', li2)

print(id(li))
print(id(li2))


# 打印结果
1472955557568
1472955557184
li: [11, 22, 33, 111]
li2: [11, 22, 33]
1472955557568
1472955557184

(2)我们看下数据嵌套的情况下

① 浅复制-----复制的数据引用

 描述:拷贝父对象,不会拷贝对象的内部的子对象。

# ----------浅复制:复制的数据引用--------------------
a = [1, 2, 3]
li = [11, 22, a]
li2 = li.copy()
print(li, id(li))
print(li2, id(li2))


# 打印结果
[11, 22, [1, 2, 3]] 2221095920896
[11, 22, [1, 2, 3]] 2221098804480

我们这个时候往li里添加一个元素看看有没有影响?答:没有任何影响。 

 

# ----------浅复制:复制的数据引用--------------------
a = [1, 2, 3]
li = [11, 22, a]
li2 = li.copy()
li.append(999)
print(li, id(li))
print(li2, id(li2))


# 打印结果
[11, 22, [1, 2, 3], 999] 1855386756352
[11, 22, [1, 2, 3]] 1855389636288

但是我如果修改 a的话,对Li2会有影响吗?答:会

 为什么呢?我们把li和li2索引为2的、a的内存地址打印出来看下,发现他们是一样的。为什么呢?

因为:浅复制,复制的是数据引用。我们把引用的对象(引用的变量)复制过来了。

 

② 深复制(对数据中的值会重新创建一份)

描述:copy 模块的 deepcopy 方法,完全拷贝了父对象及其子对象。

很明显li[2]和li2[2]的内存地址不一样了,因为他对a的值重新创建了一份。

#  ----------深复制:对数据中的值会重新创建一份--------------------
import copy

a = [1, 2, 3]
li = [11, 22, a]
li2 = copy.deepcopy(li)

print(id(a))
print(id(li[2]))
print(id(li2[2]))


# 打印结果
1779520501568
1779520501568
1779523000256

 这个时候我去修改一下a,那么我们看下对li和 li2哪个有影响。

import copy

a = [1, 2, 3]
li = [11, 22, a]
li2 = copy.deepcopy(li)

print(id(a))
print(id(li[2]))
print(id(li2[2]))

a.pop()
print('-----------')
print(li)
print(li2)


# 打印结果
1840960763712
1840960763712
1840963262528
-----------
[11, 22, [1, 2]]
[11, 22, [1, 2, 3]]

四、垃圾回收机制

(1)引用计数

之前讲对象的引用我们讲到了,每个对象创建之后都有一个引用计数,当引用计数为0的时候,那么此时的辣鸡回收机制会自动把它销毁,回收内存空间。

弊端:引用计数存在一个缺点:那就是当两个对象出现循环引用的时候,那么这个2个变量始终不会被销毁,这样子就会导致内存泄漏。

 

a = [11]
b = [1]

a.append(b)
b.append(a)

print(a)


# 打印结果 当两个对象出现循环引用的时候,那么这个2个变量始终不会被销毁,这样子就会导致内存泄漏。
[11, [1, [...]]]

(2)标记清除

(3)分代回收

对于所有有可能会被循环引用的数据,会创建第0代链表,放在里面。那么这个数据什么时候被抹除掉?先看有没有引用计数为0的,为0的就先抹除掉。如果有全局变量引用了这个对象,那我就标识一下(你是活的,你有用),然后看里面有没有引用其他数据,然后再标记一下(这是活得,有用),如果数据没有被全局变量直接引用或者间接引用它的话,那就会标记为死的。标记完了之后,没有被标记到的或者标记为死的,就直接进行垃圾回收。

我们来直接举个栗子吧。

11, 22 有引用,所以我们删除a,删除b, 他们不会被释放。因为他们有全局变量引用或者其他的引用。

 

 

 

 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值