python -- 内存管理

目录

一、python引用机制

Ⅰ、python动态类型

二、python引用计数

Ⅰ、引用计数原理

Ⅲ、增加引用计数

 Ⅳ、减少引用计数

Ⅴ、循环引用情况

Ⅵ、引用计数机制的优缺点

三、垃圾回收

Ⅰ、回收原则

Ⅱ、gc机

Ⅲ、效率问题

Ⅳ、三种情况触发垃圾回收

Ⅴ、标记清楚

Ⅵ、分代(generation)回收

四、python缓冲池

注:python深拷贝与浅拷贝


 

 

一、python引用机制

Ⅰ、python动态类型

• 对象是储存在内存中的实体。
• 我们在程序中写的对象名,只是指向这一对象的引用(reference)
• 引用和对象分离,是动态类型的核心
• 引用可以随时指向一个新的对象(内存地址会不一样)

二、python引用计数

在Python中,每个对象都有存有指向该对象的引用总数,即引用计数(reference count)。容器类型放的都是引用

Ⅰ、引用计数原理

1.每个对象维护一个 ob_ref 字段,用来记录该对象当前被引用的次数
2.当有新的引用指向的时候,引用计数+1
3.当有无效的引用发生的时候,引用计数-1
4.当对象的引用计数为0时,便会销毁对象
5.缺点:是需要额外的空间维护引用计数, 不能解决对象的“循环引用”。
Ⅱ、获取引用计数:(getrefcount)
当使用某个引用作为参数,传递给getrefcount()时,参数实际上创建了一个临时的引用。因此,
getrefcount()所得到的结果,会比期望的多1。
 

Ⅲ、增加引用计数

        当一个对象A被另一个对象B引用时,A的引用计数将增加1.
from sys import getrefcount
a = [1,2,3]
print(getrefcount(a))
b = a
print(getrefcount(a))
c = [a,a]
print(getrefcount(a))
print(getrefcount(b))
print(getrefcount(c))

2
3
5
5
2
 

 Ⅳ、减少引用计数

        del删除或重新引用时,引用计数会变化(del只是删除引用)
from sys import getrefcount
a = [1,2,3]
b = a
c = [a,a]
print(getrefcount(a))
print(getrefcount(b))
print(getrefcount(c))
del c[0]
print(c)
print(getrefcount(a))
b = 2
print(getrefcount(a))

[[1, 2, 3]]
4
3

Ⅴ、循环引用情况

        x = []
        y = []
        x.append(y)
        y.append(x)
        对于上面相互引用的情况,删除x,y引用计数都减1,但是没有归0在内存中就不会释放,程序又不能访问这片空间,造成内存泄露。

Ⅵ、引用计数机制的优缺点

优点:简单、实时性

缺点:维护引用计数消耗资源、循环引用时,无法回收。

三、垃圾回收

Ⅰ、回收原则

        当Python的某个对象的引用计数降为0时,可以被垃圾回收。

Ⅱ、gc机

• GC:现代编程语言的自动内存管理机制。
• 找到内存中无用的垃圾资源
• 清除这些垃圾并把内存让出来给其他对象使用。
GC彻底把程序员从资源管理的重担中解放出来,让其有更多的时间放在业务逻辑上。了解GC知识还是有利于我们写出更健壮的代码。

Ⅲ、效率问题

        垃圾回收时,Python不能进行其它的任务。频繁的垃圾回收将大大降低Python的工作效率。

当Python运行时,会记录其中分配对象(object allocation)和取消分配对象(object deallocation)的 次数。当两者的差值高于某个阈值时,垃圾回收才会启动。


>>> import gc
>>> print(gc.get_threshold())
(700, 10, 10)
>>>gc.collect()
2
>>>gc.collect()
0
# 700 --分配对象和取消分配对象的差值
# 10 -- 0代经历十次垃圾回收扫描存活升为1代
# 10  --  当0代经历10次扫描后才会扫描一次1代,其他带以此类推

Ⅳ、三种情况触发垃圾回收

• 调用gc.collect()
• GC达到阀值时
• 程序退出时

Ⅴ、标记清楚

        标记 - 清除机制:首先标记对象(垃圾检测),然后清除垃圾(垃圾回收).主要用于解决循环引用。
1. 标记:活动(有被引用), 非活动(可被删除)
2. 清除:清除所有非活动的对象

Ⅵ、分代(generation)回收

这一策略的基本假设是:存活时间越久的对象,越不可能在后面的程序中变成垃圾。
• Python将所有的对象分为0,1,2三代。
• 所有的新建对象都是0代对象。
• 当某一代对象经历过垃圾回收,依然存活,那么它就被归入下一代对象。
• 垃圾回收启动时,一定会扫描所有的0代对象。
• 如果0代经过一定次数垃圾回收,那么就启动对0代和1代的扫描清理。 • 当1代也经历了一定次数的垃圾回收后,那么会启动对0,1,2,即对所有对象进行扫描。

四、python缓冲池

1.整数对象缓冲池

整数缓冲区是在(-5~256)之间,系统已经初始化好,可以直接拿来用。而对于其他的大整数,系统则提 前申请了一块内存空间,等需要的时候在这上面创建大整数对象。

 

>>> a = 777
>>> b = 777
>>> id(a)
140552326841040
>>> id(b)
140552326841360
>>> a = b = 777
>>> id(a)
140552326841456
>>> id(b)
140552326841456
>>> a = 1
>>> b = 1
>>> id(a)
140552326182816
>>> id(b)
140552326182816
>>> a =  257
>>> b  =  257
>>> id(a)
140552326841040
>>> id(b)
140552326841360
>>> b  =  256
>>> a =  256
>>> id(a)
140552326190976
>>> id(b)
140552326190976
>>> a =  -5
>>> b  = -5
>>> id(a)
140552326182624
>>> id(b)
140552326182624
>>> b  = -6
>>> a =  -6
>>> id(a)
140552326841200
>>> id(b)
140552326841040
>>>
2.字符串缓存(字符串驻留区)
为了检验两个引用指向同一个对象,我们可以用is关键字。is用于判断两个引用所指的对象是否相同。
当触发缓存机制时,只是创造了新的引用,而不是对象本身。
①字符串组成:字母、数字、下划线
 
>>>a = "xxx"
>>> b = "yyy"
>>> id(a)
140552327032424
>>> id(b)
140552327032480
>>> b = "xxx"
>>> id(b)
140552327032424
>>>>>> b = "xxx"
>>> id(b)
140552327032424
>>> a = "xxx "
>>> id(a)
140552327032536
>>> b = "xxx  "
>>> id(b)
140552327032424
>>> a = "xxx_"
>>> b = "xxx_"
>>> id(a)
140552327032480
>>> id(b)
140552327032480
>>>

        当第一个字符串在创建的时候把这个内容放到了字符串驻留区,第二次被创建的时候,会先去字符串驻留区找,如果存在就指向它。不存在再创建。

②字符串驻留区字符串长度 (当字符串以*的形式创建,字符串驻留区时最多只能存20个字符)

>>> a = "x"*21
>>> b = "x"*21
>>> id(a)
140552327053224
>>> id(b)
140552327069744
>>> a = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
>>> b = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
>>> id(a)
140552327539080
>>> id(b)
140552327539080
>>>

注:python深拷贝与浅拷贝

1.数字拷贝

>>> a = 12
>>> b= a
>>> id(a)
139990825158912
>>> id(b)
139990825158912
>>>
多个引用指向同一个对象,如果一个引用值发生变化,那么实际上是让这个引用指向一个新的引用, 并不影响其他的引用的指向。

2.字典拷贝

>>> a=[[]]*3
>>> a
[[], [], []]
>>> a[0].append(1)
>>> a
[[1], [1], [1]]


>>> b=[[]]*3
>>> b
[[], [], []]
>>> b[0].append(1)
>>> b
[[1], [1], [1]]
>>> b.append(1)
>>> b
[[1], [1], [1], 1]

>>> a = {"first":[1,2,3]}
>>> a
{'first': [1, 2, 3]}
>>> b= a
>>> id(a)
140710025479512
>>> id(b)
140710025479512
>>> a[2]="No.2"
>>> a
{'first': [1, 2, 3], 2: 'No.2'}
>>> a["first"].append(4)
>>> a
{'first': [1, 2, 3, 4], 2: 'No.2'}
>>> b
{'first': [1, 2, 3, 4], 2: 'No.2'}

3.字典浅拷贝

>>> a ={"first":[1,2,3]}
>>> b = a.copy()
>>> a
{'first': [1, 2, 3]}
>>> b
{'first': [1, 2, 3]}
>>> id(a)
140710024822216
>>> id(b)
140710024820992
>>> a["seconde"] = "No.2"
>>> a
{'first': [1, 2, 3], 'seconde': 'No.2'}
>>> b
{'first': [1, 2, 3]}
>>> a["first"].append(4)
>>> a
{'first': [1, 2, 3, 4], 'seconde': 'No.2'}
>>> b
{'first': [1, 2, 3, 4]}
>>>
• 拷贝第一层数据(地址),被拷贝地址后续进行更改,不影响前面拷贝的地址。

4.字典深拷贝

>>>a = {"first":[1,2,3]}
>>> import copy
>>> b = copy.deepcopy(a)
>>> id(a)
140710024822216
>>> id(b)
140710024821064
>>> a["second"] = "No.2"
>>> a["first"].append(4)
>>> a
{'first': [1, 2, 3, 4], 'second': 'No.2'}
>>> b
{'first': [1, 2, 3]}
>>>

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAMjnlj7fku6PnoIHlsI_nmb0=,size_14,color_FFFFFF,t_70,g_se,x_16递归拷贝所有层的数据

总结:
• 数字和字符串、元组,不能改变对象本身,只能改变引用的指向,称为不可变数据对象(immutable object)。
• 列表、字典、集合可以通过引用其元素,改变对象自身(in-place change)。这种对象类型,称为可 变数据对象(mutable object)

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值