python属于动态语言,我们可以随意的创建和销毁变量,如果频繁的创建和销毁则会浪费cpu,那么python内部是如何优化的呢?

python和其他很多高级语言一样,都自带垃圾回收机制,不用我们去维护,也避免了出现内存泄漏,悬空指针等bug,那么python内部如何进行垃圾回收的呢?

python的垃圾回收,我们用gc模块去开启或者关闭它,那么gc模块又是什么呢?

python的优化机制

python垃圾回收之引用计数

关于循环引用进行一个测试

import gc  # python的一个垃圾回收模块,garbage collection

class A(object):
def __init__(self):
pass def foo():
while True:
a1 = A()
a2 = A()
a1.t = a2
a2.t = a1
del a1
del a2 gc.disable()
foo() '''
当我们使用gc.disable(),就意味着我们把python的垃圾回收机制给关闭了,那么一些垃圾便不会被自动回收。
首先我们创建了两个实例对象a1和a2,然后在a1的内部创建一个属性指向a2,在a2的内部创建一个属性指向a1
那么a1和a2的引用计数都为2,即使del a1,del a2,它们的引用计数还是为1,因此仍然会待在内存里不会被回收
当我们进行第二轮循环的时候,会继续再内存中创建一份a1和a2,不断地循环会发现程序所使用的内存也来越大,直至溢出崩溃
'''

探讨一下ruby和python的垃圾回收机制
ruby和python均属于脚本语言,它们是非常相似的,比方说定义两个类
ruby的对象分配
当我们执行上面的Node.new(1)时,ruby在内部给我们做了什么?ruby是如何创建新的对象的呢?其实ruby做的很少,实际上早在代码执行之前,ruby就创建了成百上千个对象(类似于python的小整数对象池),并把它们串在链表上,叫做可用链表
因此在创建对象的时候直接拿来用就可以了。

白色的小方格相当于标着一个“未使用预创建对象”,当我们调用Node.new方法时,ruby直接取出一个给我们使用即可

可以简单地理解,黑色格子是当前使用的对象,白色格子是未使用对象。当我们创建了一个“ABC”,放在了第一个格子的位置,让n1这个变量指向它。

由于这是一个链表,链表上的对象一般保存着下一个对象的内存指针,因此我们只需要找到一个便可以顺藤摸瓜将剩下的全给找出来,由于已经被使用,那么“箭头”也被切断了,剩下的白色格子在一个链表上,保存的是可用的内存

如果我们再次调用Node.new,ruby将继续递给我们一个对象。

因此随着我们不断地创建,ruby不断的从可用链表里取预创建对象给我们,从而也就导致了可用链表会越来越短

注意图像,我为n1不止赋了一次值,赋完值之后,又给n1赋了新的值,让n1指向了新的内存空间,那么就会有垃圾产生。

ruby的标记--清除

当我们把可用空间全部用完了,可用链表上变得空空如也

可以看到所有格子都变灰了,没有白格子了。那么ruby是如何解决这一问题的呢?

ruby使用的是标记--清除,首先ruby会把程序停下来,然后轮询所有的指针,变量和代码产生的别的引用对象以及其他值,同时ruby通过自身的虚拟机遍历内部指针,只要对象被引用,那么便将其标记(mark)出来。

只要没有被标记的,那么便是垃圾。

ruby将这些没有被标记的垃圾进行回收,并把它们送回到可用链表当中。

由于内部发生的很快,ruby不会将对象到处拷贝,而是通过调整内部的指针,将其指向一个新链表的方式,来将垃圾对象归位到可用链表当中的。

python的对象分配
我们了解了ruby是预先创建对象并将它们放在可用链表当中,那么python又是怎样的呢?
在为新对象和变量分配内存方面,ruby和python是不同的

我们用python来创一个Node对象

与ruby不同,python在创建对象时立即向操作系统申请内存(实际上python实现了一套自己的内存分配系统,在操作系统堆之上提供了一个抽象层,这方面的内容以后再说)

当我们创建第二个对象时,继续向操作系统申请内存。

给人感觉就像是,ruby在内存的申请和分配上先花了较长时间,接下来创建对象的时候效率高

python一开始并没有花费太长的时间,而是在创建的时候才会申请,相当于把时间均摊了。

当我们有三个对象时

在创建对象时,python总是在对象的C结构体里保存一个整数,就是我们说的引用数,初始值为1

但是如果我们让n1指向新的对象

那么python变把“ABC”的引用数设置为0了,因此也就变成了垃圾。此时python内部不断运转的垃圾回收机制便闪亮登场了,当引用数变为0,python会立即将其释放,把内存空间交还给操作系统

因此ruby和python关于对象创建和垃圾回收的差异就是,ruby是一开始创建一大片,一大片都用完了再回收;python是用的时候才创建,用完了立刻回收。

可以理解为ruby是房间脏了不要紧,只要还能住人,然而当房间已经脏的没法住人了,会进行一次大扫除。python是只要脏了就会立刻进行打扫。

话说,ruby貌似是日本人发明的吧,举房间的例子貌似有点乖乖的,与我印象中的日本不太一样(*^__^*,话说如果大家以后有时间,推荐去一下日本(乡下),很美)

python垃圾回收之隔代回收

 之前提到过循环引用

这种情况,python的引用计数是无法解决的,因此另一种垃圾清理方式便出现了,隔代回收

python在创建对象之前,也会创建一个链表,零代链表,只不过这个链表是空的。每当你创建一个对象,python便会将其加入到零代链表

当我们再创建一个对象时,python同样会将其加入到链表当中。

接下来检测循环引用

随后python会遍历零代链表上的每一个对象,检测链表中每个循环引用的对象,根据规则减去其引用计数。在这个过程中,python会一个接一个的统计内部对象的引用数量以防过早地释放对象。

从图中可以看到,有三个其他的对象同时存在于零代链表中,蓝色箭头表示一些对象正在被零代链表之外的某些对象引用着。python还存在一代和二代链表,这些对象有着更高的引用计数,因为它们正在被其他指针所指向着

如何处理零代链表:

从图中可以看到,"ABC","DEF"的引用计数为1,但是已经没有任何变量向它们了,因此python在达到一定条件(这里的条件后面再说)时,会将零代链表的具有循环引用的对象(python解释器不是上来就减去一,而是先判断哪些对象是被循环引用的)的引用计数统统减去1
如果引用计数为零,那么会将其作为垃圾清理掉,而那些没有被清理掉的,没有循环引用的(引用计数减一,可以看作忽视掉了一个循环引用)对象会被移动到一代链表中

同理,python还有二代链表。一代链表存放的是那些在零代链表中被清理完之后还活着的,二代链表存放的是那些在一代链表中被清理之后还活着的。为什么会有三条链子,而不是一条。

因为python在清理链子的时候,是把链子上的所有对象进行一次清理,如果已经清理一次感觉很稳定了,那么没有必要再清理了,因此将其移动到新的链子上。

因此python隔代回收的核心就是:对链子上的那些明明没有被引用但引用计数却不是零的对象进行引用计数减去一,看看你是不是垃圾。如果被引用多次减去一之后仍不为零,那么会在零代链表当中继续被清理,直至引用计数为零。因为如果没有变量指向它,或者作为函数的参数,列表的元素等等,那么它就始终是零代链表中被清理的对象。当零代链表被清理达到一定次数时(次数以及上面的条件在gc模块中会说),会清理一代链表。一代链表被清理达到一定次数时,会清理二代链表。

因此清理的频率最高的是零代链表,其次是一代链表,再是二代链表

因此关于python的垃圾回收用一句话总结就是:引用计数为主,隔代回收为辅,来完成垃圾回收。ruby则是标记清除

gc模块

再来看看这段代码,明明有垃圾回收,为什么程序的内存消耗还会不断地增加呢?因为在第19行,我们将gc(垃圾回收)的功能给关闭了

gc还可以显式的开启

因此这段程序也不会导致程序占用内存不断增加的情况产生

此外还可以通过gc.garbage,查看清理了哪些垃圾

不过gc一般默认是开启的,我们不用去关心它们。

话说还记的上面说的当达到一定条件清理零代链表,一代链表,二代链表吗?这个所谓的条件是什么呢?

在gc模块中有这样一个函数

这个元祖中有三个值,而且是不变的。第一个值700表示的是,当新创建的对象减去已经释放的对象超过700的时候,会清理零代链表

第二个值10表示的是,当零代链表清理10次的时候,会清理一次一代链表。在清理一代链表的时候,会顺便清理一次零代链表

第三个值10表示的是,当一代链表清理10次的时候,会清理一次二代链表。在清理二代链表的时候,会顺便清理一次零代链表和一代链表

也可以自己设置这个gc隔代回收触发条件的阈值

我们也可以查看当前的值是否达到了阈值

再来总结一下关于Python的引用计数

导致引用计数加一的情况:

  对象被创建,a=1

  对象被引用,b=a

  对象被作为参数传到一个函数中,func(a)

  对象作为列表,元祖等等的一个元素,li=[a,a]

导致引用计数减一的情况:

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

  对象的引用指向了新的对象,a=2

  对象离开了它的作用域,比如函数的局部变量,在函数执行完毕的时候,也会被销毁。(全局变量不会)

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

查看一个对象的引用计数:

  通过sys模块下的一个函数

  

  不过这里为什么是2,难道不是1吗?这是因为我们把a扔进了sys.getrefcount()这个函数里,从而导致引用计数加1

  可以看一下源码

  

  源码的注释里也说了,返回一个对象的引用计数,但是这个数通常比你所预想的要多1,这是因为它包含了作为参数传给getrefcount()函数的引用。

  

关于gc,还有一个重要的一点。

gc进行清理对象的时候,本质上调用了对象内部的析构函数__del__,但是如果我们重写了,那么它就不会执行自己的,或者父类的,那么对象也不会被清理掉。因此当我们在定义一个类,要重写析构函数时,当我们写完我们的代码时,最好让它再执行父类的析构方法。

这也算是gc的一个bug,不过这个bug只在python2中才会有,在我用的python3.6中不会出现这个bug,因此如果不是用python2的小伙伴的话,就不用担心这个问题啦·········

以上就是关于python的垃圾回收的一些理解··············

python的优化机制与垃圾回收与gc模块的更多相关文章

  1. Python的内存管理和垃圾回收机制

    内存管理 Python解释器由c语言开发完成,py中所有的操作最终都由底层的c语言来实现并完成,所以想要了解底层内存管理需要结合python源码来进行解释. 1. 两个重要的结构体 include/o ...

  2. JVM性能优化--Java的垃圾回收机制

    一.Java内存结构 1.Java堆(Java Heap) java堆是java虚拟机所管理的内存中最大的一块,是被所有线程共享的一块内存区域,在虚拟机启动时创建.此内存区域的唯一目的就是存放对象实例 ...

  3. 【python进阶】Garbage collection垃圾回收2

    前言 在上一篇文章[python进阶]Garbage collection垃圾回收1,我们讲述了Garbage collection(GC垃圾回收),画说Ruby与Python垃圾回收,Python中 ...

  4. 转 Java虚拟机5:Java垃圾回收(GC)机制详解

    转 Java虚拟机5:Java垃圾回收(GC)机制详解 Java虚拟机5:Java垃圾回收(GC)机制详解 哪些内存需要回收? 哪些内存需要回收是垃圾回收机制第一个要考虑的问题,所谓“要回收的垃圾”无 ...

  5. JVM内存管理机制和垃圾回收机制

    JVM内存管理机制和垃圾回收机制 JVM结构 图片描述: java源码编译成class文件 class文件通过类加载器加载到内存 其中方法区存放的是运行时的常量.静态变量.类信息等,被所有线程共享 堆 ...

  6. 基于Python对象引用、可变性和垃圾回收详解

    基于Python对象引用.可变性和垃圾回收详解 下面小编就为大家带来一篇基于Python对象引用.可变性和垃圾回收详解.小编觉得挺不错的,现在就分享给大家,也给大家做个参考. 变量不是盒子 在示例所示 ...

  7. JVM 垃圾回收(GC)机制

    目录 一.背景 二. 哪些内存需要回收? 1.引用计数算法 2 .可达性分析算法 三. 四种引用状态 1.强引用 2.软引用 3.弱引用 4.虚引用 对象死亡(被回收)前的最后一次挣扎 方法区如何判断 ...

  8. Java的垃圾回收机制:强制回收System.gc() Runtime.getTime().gc()

    垃圾回收 当引用类型的实体,如对象.数组等不再被任何变量引用的时候.这块占用的内存就成为了垃圾.JVM会根据自己的策略决定是回收内存 注意: 垃圾回收只回收内存中的对象,无法回收物理资源(数据库连接, ...

  9. 托管堆和垃圾回收(GC)

    一.基础 首先,为了深入了解垃圾回收(GC),我们要了解一些基础知识: CLR:Common Language Runtime,即公共语言运行时,是一个可由多种面向CLR的编程语言使用的"运 ...

随机推荐

  1. Python 文本挖掘:使用情感词典进行情感分析(算法及程序设计)

    出处:http://www.ithao123.cn/content-242299.html 情感分析就是分析一句话说得是很主观还是客观描述,分析这句话表达的是积极的情绪还是消极的情绪.   原理 比如 ...

  2. 05,Python网络爬虫之三种数据解析方式

    回顾requests实现数据爬取的流程 指定url 基于requests模块发起请求 获取响应对象中的数据 进行持久化存储 其实,在上述流程中还需要较为重要的一步,就是在持久化存储之前需要进行指定数据 ...

  3. border与background定位

    1.background定位的局限 只能相对于左上角数值定位,不能相对于右下 即background-position默认相对于左上方定位的 2.怎样让图片相对于右下角? background-pos ...

  4. 获取<考试>博文密码!o(*≧▽≦)ツ

    就是CJ高二组通用的密码 如果你想知道,请联系QQ,3057244225,或者直接面对面问博主(...) 是我们的内部材料,原创题目是不能外传的.请谅解. 当然如果是原题的话我们是不会上锁的啦

  5. laravel5.5队列

    目录 简单实例 1. 简介和配置 1.1 好处 1.2 配置文件 1.3 队列驱动的必要配置 2. 创建任务 2.1 生成任务类 2.2 修改任务类 2.3 分发任务 2.4 自定义队列 & ...

  6. 去掉referer信息

    <iframe src="auto-refresh.html" width=500 height=500 rel="noreferrer">< ...

  7. nginx清除反向代理缓存

    nginx重启无法清除反向代理的缓存,可以清空安装目录下的proxy_cache文件夹里的内容来清除.

  8. ptmalloc,tcmalloc和jemalloc内存分配策略研究 ? I'm OWen..

    转摘于http://www.360doc.com/content/13/0915/09/8363527_314549949.shtml 最近看了glibc的ptmaoolc,Goolge的tcmall ...

  9. uiautomator+cucumber实现移动app自动化测试

    前提 由于公司业务要求,所以自动化测试要达到以下几点: 跨应用的测试 测试用例可读性强 测试报告可读性强 对失败的用例有截图保存并在报告中体现 基于以上几点,在对自动化测试框架选型的时候就选择了uia ...

  10. excel批量导入

    https://www.cnblogs.com/mingyue1818/p/4828865.html