笔记-python-内存管理

1.      内存使用

1.1.    对象的内存使用

a = 1

1是一个对象,a是引用,指向1。

>>> id(a)

1951821280

这个数字代表内存地址;

在Python中,整数和短小的字符,Python都会缓存这些对象,以便重复使用。当我们创建多个等于1的引用时,实际上是让所有这些引用指向同一个对象。

下面进行测试:

a = 1

b = 1

print(id(a), id(b), a is b)

>>>

1951821280 1951821280 True

这点需要特别注意,特别是在多重数组创建时;

在Python中,每个对象都有存有指向该对象的引用总数,即引用计数(reference count)

可以使用sys包中的getrefcount(),来查看某个对象的引用计数。需要注意的是,当使用某个作为参数,传递给getrefcount()时,参数实际上创建了一个临时的引用;所以,getrefcount()所得到的结果,会比期望的多1.

from sys import getrefcount

a = [1, 2, 3]

print(getrefcount(a))

b = a

print(getrefcount(b))

输出是2和3,而非1和2

1.2.    对象引用对象

Python的一个容器对象(container),比如表、词典等,可以包含多个对象,实际上,容器对象中包含的并不是元素对象本身,而是指向各个元素对象的引用。

class from_obj(object):

def __init__(self, to_obj):

self.to_obj = to_obj

b = [1,2,3]

a = from_obj(b)

print(id(a.to_obj))

print(id(b))

结果是

878391926216

878391926216

显然,a引用了b。

当一个对象A被另一个对象B引用时,A的引用计数将增加1.

容器的引用可能构成复杂的拓扑。

1.3.    引用环

引用环:reference cycle两个对象可能相互引用,构成引用环。

a = []

b = [a]

a.append(b)

即使是一个对象,只需要自己引用自己,也能构成引用环。

a = []

a.append(a)

print(getrefcount(a))

引用环会给垃圾回收机制带来很大的麻烦。

1.4.    引用减少

当然,引用计数是会减少的,比如,删除引用;

from sys import getrefcount

a = [1, 2, 3]

b = a

print(getrefcount(b))

del a

print(getrefcount(b))

或者,引用指向其它对象;

from sys import getrefcount

a = [1, 2, 3]

b = a

print(getrefcount(b))

a = 1

print(getrefcount(b))

2.     
格式

2.1.   
格式

2.1.1.  
格式

3.     
内存回收

内存不是无限的,很多对象也不会一直占用内存,因此,在合适的时候需要内存回收garbage colleciton;

3.1.   
引用计数

Python中的垃圾回收是以引用计数为主,分代收集为辅。引用计数的缺陷是循环引用的问题。
在Python中,如果一个对象的引用数为0,Python虚拟机就会回收这个对象的内存。

回收垃圾无疑需要占用处理能力,垃圾回收时,Python不能进行其它的任务。频繁的垃圾回收将大大降低Python的工作效率,如果内存中的对象不多,就没有必要总启动垃圾回收。

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

可以通过gc模块的get_threshold()方法,查看该阈值:

import gc

print(gc.get_threshold())

返回(700, 10, 10),后面的两个10是与分代回收相关的阈值,后面可以看到。700即是垃圾回收启动的阈值。可以通过gc中的set_threshold()方法重新设置。

也可以手动启动垃圾回收,即使用gc.collect()。

导致引用计数+1的情况

对象被创建,例如a=23

对象被引用,例如b=a

对象被作为参数,传入到一个函数中,例如func(a) 对象作为参数传入到一个函数中会 +2

对象作为一个元素,存储在容器中,例如list1=[a,a]

导致引用计数-1的情况

对象的别名被显式销毁,例如del a

对象的别名被赋予新的对象,例如a=24

一个对象离开它的作用域,例如f函数执行完毕时,func函数中的局部变量(全局变量不会)

对象所在的容器被销毁,或从容器中删除对象

3.1.1.   
分代回收

Python同时采用了分代(generation)回收的策略。这一策略的基本假设是,存活时间越久的对象,越不可能在后面的程序中变成垃圾。程序往往会产生大量的对象,许多对象很快产生和消失,但也有一些对象长期被使用。出于信任和效率,对于这样一些“长寿”对象,我们相信它们的用处,所以减少在垃圾回收中扫描它们的频率。

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代垃圾回收。

同样可以用set_threshold()来调整,比如对2代对象进行更频繁的扫描。

import gc

gc.set_threshold(700, 10, 5)

3.2. 
引用环

引用环的存在会给上面的垃圾回收机制带来很大的困难。这些引用环可能构成无法使用,但引用计数不为0的一些对象。

a = []

b = [a]

a.append(b)

del a

del b

从需求来看,上面两个对象不再使用,应该释放对应的资源;但由于引用环,这两个对象的引用计数都没有降到0,不会被回收;

为了回收这样的引用环,Python复制每个对象的引用计数,可以记为gc_ref。假设,每个对象i,该计数为gc_ref_i。Python会遍历所有的对象i。对于每个对象i引用的对象j,将相应的gc_ref_j减1。

在结束遍历后,gc_ref不为0的对象,和这些对象引用的对象,以及继续更下游引用的对象,需要被保留。而其它的对象则被垃圾回收。

最新文章

  1. 【腾讯优测干货分享】如何降低App的待机内存(二)——规范测试流程及常见问题
  2. Java数组的一些基本算法
  3. Hadoop集群中添加硬盘
  4. SQLMap使用
  5. [leetcode] 405. Convert a Number to Hexadecimal
  6. 转:PHP include()和require()方法的区别
  7. UVA 839 (13.08.20)
  8. mac下使用命令行打包出现bash gradle command not found的解决方案
  9. AB PLC分类
  10. 洗礼灵魂,修炼python(42)--巩固篇—type内置函数与类的千丝万缕关系
  11. PAT 1039 到底买不买
  12. JVM内存管理(转)
  13. 牛客练习赛26 xor序列
  14. 1120 Friend Numbers (20 分)
  15. java学习第四周
  16. Java中list如何利用遍历进行删除操作
  17. softmax 杂谈
  18. WPF Binding(四种模式)
  19. python3第一次作业
  20. java 反射 获取Class对象的三种方式

热门文章

  1. 禁止form重复提交
  2. Web 前端安装依赖的时候遇到的问题
  3. Windows、Unix、Mac不同操作系统的换行问题-剖析回车符\r和换行符\n
  4. webpack源码之ast简介
  5. 锁丶threading.local丶线程池丶生产者消费者模型
  6. VC使用编译时间作为版本号
  7. 利用python进行简单的图片处理
  8. AngularJS 整理学习
  9. 五、c++实现离散傅里叶变换
  10. IOS PickerView使用