一、python的内存管理

  • python内部将所有类型分成两种,一种由单个元素组成,一种由多个元素组成。利用不同结构体进行区分

    /* Nothing is actually declared to be a PyObject, but every pointer to
    * a Python object can be cast to a PyObject*. This is inheritance built
    * by hand. Similarly every pointer to a variable-size Python object can,
    * in addition, be cast to PyVarObject*.
    */
    typedef struct _object {
    _PyObject_HEAD_EXTRA // 双向链表
    Py_ssize_t ob_refcnt; // 引用计数器,整型
    struct _typeobject *ob_type; // 表示对象的类型
    } PyObject; //单个元素的 typedef struct {
    PyObject ob_base; # PyObject结构体,内部包含引用计数器,双向链表,对象类型
    Py_ssize_t ob_size; /* Number of items in variable part */ # 该对象由多少的元素组成
    } PyVarObject; //多个元素的 /* Define pointers to support a doubly-linked list of all live heap objects. */
    //翻译:定义指针以支持双向链表
    #define _PyObject_HEAD_EXTRA
    struct _object *_ob_next; //下一个
    struct _object *_ob_prev; //上一个
  • 在python代码中如果进行创建对象或者对对象赋值操作,内存会对对象做如下操作,也是python中的引用计数方式。

    • 如果双向链表中已经存在该对象则只将该对象的引用计数加1;
    • 如果双向链表中不存在,则向双向链表中添加该对象,并将对应的引用计数设置为1。
    • 如果执行了对象的删除,引用计数器减1,如果引用计数器为0,就将它从双向链表中移除

二、垃圾回收--引用计数

  • Python中的垃圾回收机制是采用引用计数为主、标记清除和分代回收为辅的方式。

  • 创建对象或者对对象赋值,引用计数加1,删除对象,则引用计数器减1;当一个对象的引用计数为0时, Python的垃圾回收机制就会将对象回收,即从双向链表中移除。

    a ="xiaoqi"
    b = a

    xiaoqi这个字符串对象, 在第一行被a变量引用后, 该对象引用计数为1,之后在第二行, 将a赋值给b,实际上是a引用的对象又被b引用, 此时, 该字符串对象的引用计数为2。

  • 删除对象

    a = "xiaoqi"
    b = a
    del a # 此时"xiaoqi"引用计数应该是1

    注意: 在Python语言中, del语句操作某个对象的时候, 并不是直接将该对象在内存中删除, 而是将该对象的引用计数-1,只是删除变量a对字符串的引用,实际上字符串并没有在内存删除,相当于工作岗位是有的,只是单独的解除了劳动合同。

    >>> a = "xiaoqi"
    >>> b = a
    >>> del a
    >>> id(b)
    4572141808
    >>> id(a)
    Traceback (most recent call last):
    File <input>, line 1, in <module>
    id(a)NameError: name 'a' is not defined
  • 从以上示例中可以看出,xiaoqi这个字符串对象在第一行被变量a引用,此时字符串对象的引用计数为1,接着第二行又被变量b引用,此时该字符串对象的引用计数为2,在第三行中,del语言删除了a变量(标签),在后续的print中可以看出,内存中实际的字符串对象并没有被删除,del命令只是删除了一个变量对该字符串对象的引用,所以对于xiaoqi这个字符串对象来说, 效果只是引用计数-1

引用计数案例

import sys
class A:
def __init__(self):
'''初始化对象'''
print('object born id:%s' %str(hex(id(self)))) # 对象被引用增加一次 def f1():
'''循环引用变量与删除变量'''
while True:
c1=A()
del c1 def func(c):
print('obejct refcount is: ',sys.getrefcount(c)) #getrefcount()方法用于返回对象的引用计数
# 注:getrefcount()方法执行时,引用计数会+1,所以实际值应该是获取的结果-1 if __name__ == '__main__':
#生成对象
a=A() # 创建对象引用计数+1
func(a) # 作为参数传给函数引用计数+1 #赋值引用计数+1
b=a
func(a) #销毁引用对象b
del b
func(a)
# 打印结果object born id:0x265c56a56d8
obejct refcount is: 4
obejct refcount is: 5
obejct refcount is: 4
# 上述列子证明,同一个方式重复引用同一个对象引用计数是不会改变的

导致引用计数+1的情况

对象被创建,例如a=23
对象被引用,例如b=a
对象被作为参数,传入到一个函数中,例如func(a)
对象作为一个元素,存储在容器中,例如list1=[a,a]

导致引用计数-1的情况

对象的别名被显式销毁,例如del a
对象的别名被赋予新的对象,例如a=24
一个对象离开它的作用域,例如func函数执行完毕时,func函数中的局部变量引用计数会将少(全局变量不会)
对象所在的容器被销毁,或从容器中删除对象

注:重复相同的操作引用计数不变,如上面例子,多次执行func(a),并不会改变计数。 

魔法函数之__del__

类中的__del__魔法函数, 支持我们自定义清理对象的逻辑, 当Python解释器使用del语言删除类的对象的时候, 会自动调用类中的__del__函数, 我们可以对其进行重载

>>> class Ref:
...
... def __init__(self, name):
... self.name = name
...
... def __del__(self):
... print("删除对象")
... del self.name
>>>
>>> r = Ref(name="xiaoqi")
>>> print(r.name)
xiaoqi
>>>
>>> del r

删除对象

>>> print(r.name)
Traceback (most recent call last):
File "<input>", line 1, in <module>
print(r.name)
NameError: name 'r' is not defined

我们可以通过重载__del__魔法函数,自己灵活控制在del 对象的时候执行哪些操作。

三、循环引用

  • 垃圾回收机制中,循环引用会导致内存泄露,举例说明

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    import gc
    import objgraph class Foo(object):
    def __init__(self):
    self.data = None # 在内存创建两个对象,即:引用计数器值都是1
    obj1 = Foo()
    obj2 = Foo() # 两个对象循环引用,导致内存中对象的应用+1,即:引用计数器值都是2
    obj1.data = obj2
    obj2.data = obj1 # 删除变量,并将引用计数器-1,各自的引用计数还剩1。
    del obj1
    del obj2 # 关闭垃圾回收机制,因为python的垃圾回收机制是:引用计数器、标记清除、分代回收 配合已解决循环引用的问题,关闭他便于之后查询内存中未被释放对象。
    gc.disable() # 至此,由于循环引用导致内存中创建的obj1和obj2两个对象引用计数器不为0,无法被垃圾回收机制回收。
    # 所以,内存中Foo类的对象就还显示有2个。
    print(objgraph.count('Foo')) # 查看 # 为了进行对比,不循环引用的情况下执行后的结果为0,说明循环引用导致内存泄露。
    import gc
    import objgraph class Foo(object):
    def __init__(self):
    self.data = None obj1 = Foo()
    obj2 = Foo() del obj1
    del obj2 gc.disable()
    print(objgraph.count('Foo')) # 0
  • gc模块

    enable() -- 启用自动垃圾收集
    disable() -- 禁用自动垃圾收集
    isenabled() -- 如果启用自动收集,则返回true。
    collect() -- 现在就做一个完整的收集,即主动触发垃圾回收机制。
    get_count() -- 返回当前集合计数。
    get_stats() -- 返回包含每代统计信息的词典列表
    get_referrers() -- 返回引用对象的对象列表
    get_referents() -- 返回对象引用的对象列表
  • objgraph模块

    objgraph的实现调用了gc的这几个函数:gc.get_objects(), gc.get_referents(), gc.get_referers(),然后构造出对象之间的引用关系。objgraph的代码和文档都写得比较好,建议一读。
    
    下面先介绍几个十分实用的API
    def count(typename)
    返回该类型对象的数目,其实就是通过gc.get_objects()拿到所用的对象,然后统计指定类型的数目。 def by_type(typename)
    返回该类型的对象列表。线上项目,可以用这个函数很方便找到一个单例对象 def show_most_common_types(limits = 10)
    打印实例最多的前N(limits)个对象,这个函数非常有用。在《Python内存优化》一文中也提到,该函数能发现可以用slots进行内存优化的对象 def show_growth()
    统计自上次调用以来增加得最多的对象,这个函数非常有利于发现潜在的内存泄露。函数内部调用了gc.collect(),因此即使有循环引用也不会对判断造成影响。

循环引用的问题会引发内存中的对象一直无法释放,从而内存逐渐增大,最终导致内存泄露。由于循环引用的原因出现标记清除。

四、垃圾回收--标记清除&分代回收

标记清除

  • "标记-清除"是python为了解决循环引用的问题而出现,针对 list, tuple, instance, classe, dict, and function 等多个元素组成的类型,每创建一个对象都会将对象放到一个双向链表中,每个对象中都有 _ob_next 和 _ob_prev 指针,用于挂靠到链表中。

  • python代码中如果进行创建对象或者对对象赋值等操作时,都会进行如下操作;

    • 如果双向链表中已经存在该对象则只将该对象的引用计数加1;
    • 如果双向链表中不存在,则向双向链表中添加该对象,并将对应的引用计数设置为1。
    • 如果执行了对象的删除,引用计数器减1,如果引用计数器为0,就将它从双向链表中移除

随着对象的创建,该双向链表上的对象会越来越多。

  • 当对象个数超过 700个 时,Python解释器就会进行垃圾回收。
  • 当代码中主动执行 gc.collect() 命令时,Python解释器就会进行垃圾回收。
import gc

gc.collect()

Python解释器在垃圾回收时,会遍历链表中的每个对象,如果存在循环引用,就将存在循环引用的对象的引用计数器 -1,同时Python解释器也会将计数器等于0(可回收)和不等于0(不可回收)的一分为二,把计数器等于0的所有对象进行回收,把计数器不为0的对象放到另外一个双向链表表(即:分代回收的下一代)。

官方文档:https://docs.python.org/3/library/gc.html

分代回收

The GC classifies objects into three generations depending on how many collection sweeps they have survived. New objects are placed in the youngest generation (generation 0). If an object survives a collection it is moved into the next older generation. Since generation 2 is the oldest generation, objects in that generation remain there after a collection. In order to decide when to run, the collector keeps track of the number object allocations and deallocations since the last collection. When the number of allocations minus the number of deallocations exceeds threshold0, collection starts. Initially only generation 0 is examined. If generation 0 has been examined more than threshold1 times since generation 1 has been examined, then generation 1 is examined as well. Similarly, threshold2 controls the number of collections of generation 1 before collecting generation 2.

翻译:gc将对象分为三代,这取决于它们保存了多少个集合清理。新对象放置在最年轻的一代(第0代)中。如果一个对象在一个集合中存活下来,它将被移动到下一代中。由于第2代是最早的一代,因此该代中的对象在收集后仍保留在那里。为了决定何时运行,收集器会跟踪自上次收集以来对象分配和释放的数量。当分配数减去释放数超过阈值0时,将开始收集。最初只检查第0代。如果自检查第1代以来检查第0代的次数超过阈值1次,则也检查第1代。类似地,threshold2在收集第2代之前控制第1代的集合数。

分代回收的出现是为了解决标记清除花费时间太长的原因。

分代技术是一种典型的以空间换时间的技术。这种思想简单点说就是:对象存在时间越长,越可能不是垃圾,应该越少去收集。

  1. 这样的思想,可以减少标记-清除机制所带来的额外操作。分代就是将回收对象分成数个代,每个代就是一个链表;
  2. 代进行标记-清除的时间与代内对象存活时间成正比例关系。

    3.当链表中对象数达到阈值,则触发垃圾回收机制,Python解释器也会将计数器等于0(可回收)和不等于0(不可回收)的一分为二,把计数器等于0的所有对象进行回收,把计数器不为0的对象放到另外一个双向链表表,即放到下一代中。
  3. python里一共有三代,0代、1代2代。有三个阈值(700,10,10),700指链表中对象个数达到700时触发垃圾回收机制,第一个10是当第0代扫描10次第1代扫描1次,第二个10是当第1代扫描10次第2代扫描1次。
# 默认情况下三个阈值为 (700,10,10) ,也可以主动去修改默认阈值。
import gc gc.set_threshold(threshold0[, threshold1[, threshold2]])

python内存管理及垃圾回收的更多相关文章

  1. Python 内存管理与垃圾回收

    Python 内存管理与垃圾回收 参考文献:https://pythonav.com/wiki/detail/6/88/ 引用计数器为主标记清除和分代回收为辅 + 缓存机制 1.1 大管家refcha ...

  2. Python内存管理:垃圾回收

    http://blog.csdn.net/pipisorry/article/details/39647931 Python GC主要使用引用计数(reference counting)来跟踪和回收垃 ...

  3. Java内存管理和垃圾回收

    笔记,深入理解java虚拟机 Java运行时内存区域 程序计数器,线程独占,当前线程所执行的字节码的行号指示器,每个线程需要记录下执行到哪儿了,下次调度的时候可以继续执行,这个区是唯一不会发生oom的 ...

  4. 面试题之C# 内存管理与垃圾回收

    面试题之C# 内存管理与垃圾回收 你说说C# 的内存管理是怎么样的 这句话我记了一个多礼拜了, 自从上次东北师大面试之后, 具体请看<随便扯扯东北师大的面试>. 国庆闲着没事, 就大概了解 ...

  5. Java内存管理及垃圾回收总结

    概述 Java和C++的一个很重要的差别在于对内存的管理.Java的自己主动内存管理及垃圾回收技术使得Java程序猿不须要释放废弃对象的内存.从而简化了编程的过程.同一时候也避免了因程序猿的疏漏而导致 ...

  6. C#内存管理与垃圾回收

    垃圾回收还得从根说起,就像生儿育女一样. 根:根是一个位置,存放一个指针,该指针指向托管堆中的一个对象,或是一个空指针不指向任何对象,即为null.根存在线程栈或托管堆中,大部分的跟都在线程栈上,因为 ...

  7. 使用虚幻引擎中的C++导论(四-内存管理与垃圾回收)(终)

    使用虚幻引擎中的C++导论(四)(终) 第一,这篇是我翻译的虚幻4官网的新手编程教程,原文传送门,有的翻译不太好,但大体意思差不多,请支持我O(∩_∩)O谢谢. 第二,某些细节操作,这篇文章省略了,如 ...

  8. Java之美[从菜鸟到高手演变]之JVM内存管理及垃圾回收

    很多Java面试的时候,都会问到有关Java垃圾回收的问题,提到垃圾回收肯定要涉及到JVM内存管理机制,Java语言的执行效率一直被C.C++程序员所嘲笑,其实,事实就是这样,Java在执行效率方面确 ...

  9. javascript中的内存管理和垃圾回收

    前面的话 不管什么程序语言,内存生命周期基本是一致的:首先,分配需要的内存:然后,使用分配到的内存:最后,释放其内存.而对于第三个步骤,何时释放内存及释放哪些变量的内存,则需要使用垃圾回收机制.本文将 ...

随机推荐

  1. 修改 IIS 默认文件上传大小

    IIS 7 默认文件上传大小是30M 要突破这个限制: 修改IIS的applicationhost.config 打开 c:/windows/system32/inetsrv/config/appli ...

  2. windows下nvm的安装及使用

    由于更新了npm版本之后导致npm的命令都会报错,一顿百度,明白了nvm可以管理node版本的,下面是操作过程: 如果在安装nvm之前已经下载了node 需要把node卸载!!! 需要把node卸载! ...

  3. Linux拷贝、移动、删除

    cp:拷贝文件或文件夹(copy) - cp original_filename copy_filename(在当前目录生成拷贝文件,并改名为copy_filename) - cp original_ ...

  4. eclipse安装weblogic Server服务器

    1.首先打开eclipse,第一次进入欢迎画面点击上方标签X,关闭欢迎标签 2.关闭欢迎标签后,进入eclipse操作界面,在上方的菜单栏,选择windows下拉菜单,选择子菜单Preference ...

  5. vue动态设置Iview的多个Input组件自动获取焦点

    1.html,通过ref=replyBox设置焦点元素,以便后续获取 // 动态设定自动获取焦点按钮 <p class="text-right text-blue fts14 ptb1 ...

  6. 2019-9-2-win10-uwp-标题栏

    title author date CreateTime categories win10 uwp 标题栏 lindexi 2019-09-02 12:57:38 +0800 2018-2-13 17 ...

  7. SELECT INTO - 从一个查询的结果中创建一个新表

    SYNOPSIS SELECT [ ALL | DISTINCT [ ON ( expression [, ...] ) ] ] * | expression [ AS output_name ] [ ...

  8. Codeforces Round #393 (Div. 2) - C

    题目链接:http://codeforces.com/contest/760/problem/C 题意:有n个烤串,并且每个烤串起初都放在一个火盆上并且烤串都正面朝上,现在定义p序列,p[i]表示在i ...

  9. Go copy 的使用

    copy 可以将后面的 第2个切片的元素赋值copy 到第一个切片中 package main; import "fmt" func test () { s1 := []int{1 ...

  10. JavaScript之ECMAScript

    JavaScript脚本语言, 运行在浏览器上,无需编译, 轻量级的语言. 功能:让页面有执行逻辑的功能, 可以产生一些动态的效果 JavaScript = ECMAScript + BOM + DO ...