zoukankan      html  css  js  c++  java
  • Python内存管理

    Python是引用、对象分离的语言,在Python中每个对象都存有指向该对象的引用总数,即引用计数

    sys的getrefcount(),查看某个对象的引用计数,当使用某个引用作为参数,传递给getrefcount()时,参数实际上创建了一个临时的引用。因此,getrefcount()所得到的结果,会比期望的多1

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

    容器对象的引用可能构成很复杂的拓扑结构,可以用objgraph包来绘制其引用关系

    两个对象可能相互引用,从而构成所谓的引用环,即使是一个对象自己引用自己也能构成引用环

    使用del关键字删除某个引用,则该对象的引用计数就会减少

    如果某个引用指向对象A,当这个引用被重新定向到某个其他对象B时,对象A的引用计数减少

    垃圾回收:

    当Python的某个对象的引用计数降为0时,说明没有任何引用指向该对象,该对象就成为要被回收的垃圾了。删除引用后,当垃圾回收启动时,Python扫描到这个引用计数为0的对象,就将它所占据的内存清空。

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

    手动回收:gc.collect()。

    分代回收:

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

    Python将所有的对象分为0,1,2三代。所有的新建对象都是0代对象。当某一代对象经历过垃圾回收,依然存活,那么它就被归入下一代对象。垃圾回收启动时,一定会扫描所有的0代对象。如果0代经过一定次数垃圾回收,那么就启动对0代和1代的扫描清理。当1代也经历了一定次数的垃圾回收后,那么会启动对0,1,2,即对所有对象进行扫描。

    孤立的引用环:

    引用环可能构成无法使用,但引用计数不为0的一些对象。

    Python会复制每个对象的引用计数,比如有两个相互引用的对象a和b,此时a的引用计数我们用gc_ref_a 来表示,同理用gc_ref_b 来表示b的引用计数,然后Python会遍历所有的引用对象,这里只有a和b,遍历到a的时候,a指向b,将 b的gc_ref_b的值减1,同理遍历b的时候将a的gc_ref_a的值减1,结果他们的值都为0,最后将不为0的对象保留,为0 的对象进行垃圾回收。

     

    引用计数:是一种垃圾收集机制,而且也是一种最直观,最简单的垃圾收集技术, 当一个对象的引用被创建或者复制时,对象的引用计数加 1;当一个对象的引用被销毁时,对象的引用计数减 1;当对象的引用计数减少为 0 时,就意味着对象已经没有被任何人使用了,可以将其所占用的内存释放了。虽然引用计数必须在每次分配和释放内存的时候加入管理引用计数的动作,然而与其他主流的垃圾收集技术相比,引用计数有一个最大的有点,即“实时性”,任何内存,一旦没有指向它的引用,就会立即被回收。而其他的垃圾收集计数必须在某种特殊条件下(比如内存分配失败)才能进行无效内存的回收。

    引用计数机制执行效率问题:引用计数机制所带来的维护引用计数的额外操作与 Python 运行中所进行的内存分配和释放,使用赋值的次数是成正比的。而这点相比其他主流的垃圾回收机制,比如“标 记-清除”,“停止-复制”,是一个弱点,因为这些技术所来的额外操作基本上只是与待回收的内存数量有关。

    引用计数机制还存在着一个 致命的弱点就是循环引用(也称交叉引用)。

    循环引用可以使一组对象的引用计数不为 0,然而这些对象实际上并没有被任何外部对象所引用, 它们之间只相互引用。这意味着不会再有人使用这组对象,应该回收这组对象所占用的内存空间,由于相互引用的存在,每一个对象的用计数都不为 0,因此这些对象所占用的内存永远不会被释放。 比如:这一点是致命的,这与手动进行内存管理所产生的内存泄露毫无区别。

    要解决这个问题,Python 引入了其他的垃圾收集机制来弥补引用计数的缺陷:“标记-清除”,“分代回收”两种收集技术。

    标记-清除:标记-清除”是为了解决循环引用的问题。可以包含其他对象引用的容器对象(比如:list, set,dict,class,instance)都可能产生循环引用。

    如果两个对象的引用计数都为 1,但是仅仅存在他们之间的循环引用,那 么这两个对象都是需要被回收的,也就是说,它们的引用计数虽然表现为非 0,但实际上有效的引用计数为0。必须先将循环引用摘掉,那么这两个对象的有效计数就现身了。假设两个对象为 A、B, 我们从 A 出发,因为它有一个对 B 的引用,则将 B 的引用计数减 1;然后顺着引用达到 B,因为 B 有一 个对 A 的引用,同样将 A 的引用减 1,这样,就完成了循环引用对象间环摘除。

    但是这样就有一个问题,假设对象 A 有一个对象引用 C,而 C 没有引用 A,如果将 C 计数引用减 1, 而最后 A 并没有被回收,显然,我们错误的将 C 的引用计数减 1,这将导致在未来的某个时刻出现一个对 C 的悬空引用。这就要求我们必须在 A 没有被删除的情况下复原 C 的引用计数,如果采用这样的方案,那么维护引用计数的复杂度将成倍增加。

    原理:“标记-清除”采用了更好的做法,我们并不改动真实的引用计数,而是将集合中对象的引用计数复制一份副本,改动该对象引用的副本。对于副本做任何的改动,都不会影响到对象生命走起的维 护。

    这个计数副本的唯一作用是寻找 root object 集合(该集合中的对象是不能被回收的)。当成功寻找到 root object 集合之后,首先将现在的内存链表一分为二,一条链表中维护 root object 集合,成为 root 链表,而另外一条链表中维护剩下的对象,成为 unreachable 链表。之所以要剖成两个链表, 是基于这样的一种考虑:现在的 unreachable 可能存在被 root 链表中的对象,直接或间接引用的对象, 这些对象是不能被回收的,一旦在标记的过程中,发现这样的对象,就将其从 unreachable 链表中移到 root 链表中;当完成标记后,unreachable 链表中剩下的所有对象就是名副其实的垃圾对象了,接下来的垃圾回收只需限制在 unreachable 链表中即可。

    从前面“标记-清除”这样的垃圾收集机制来看,这种垃圾收集机制所带来的额外操作实际上与系统中总的内存块的数量是相关的,当需要回收的内存块越多时,垃圾检测带来的额外操作就越多,而垃圾回收带来的额外操作就越少;反之,当需回收的内存块越少时,垃圾检测就将比垃圾回收带来更多的额外操作。为了提高垃圾收集的效率,采用“空间换时间的策略”。

    原理:将系统中的所有内存块根据其存活时间划分为不同的集合,每一个集合就成为一个“代”, 垃圾收集的频率随着“代”的存活时间的增大而减小。也就是说,活得越长的对象,就越不可能是垃圾, 就应该减少对它的垃圾收集频率。那么如何来衡量这个存活时间:通常是利用几次垃圾收集动作来衡量, 如果一个对象经过的垃圾收集次数越多,可以得出:该对象存活时间就越长。

     

     

  • 相关阅读:
    mysql查找数据库中是否已经存在某张表
    springboot中generator相关配置文件
    同步网络时间到linux服务器(先修改时区再进行同步网络时间)
    踩坑记:前后端分离的项目启动时间过长
    踩坑记:mysql timeStamp默认值0000-00-00 00:00:00 报错
    一次踩坑记录(使用rpc前后端分离服务总是注册不上)
    Maven项目结合POI实现导入导入导入导入导入Excl表格Demo-亲测可用
    Maven项目结合POI导出Excl表格Demo-亲测可用
    集合系列之fail-fast 与fail-safe 区别
    并发编程JUC系列AQS(CountDownLatch、CyclicBarrier、Semaphore)
  • 原文地址:https://www.cnblogs.com/liushoudong/p/12585223.html
Copyright © 2011-2022 走看看