Python垃圾回收,搞定面试

简介: 一句话: Pthon的垃圾回收机制使用的是引用计数为主,清楚标记算法和分代为辅

Python垃圾回收机制


1 引用计数:


Python采用引用计数的方式对对象进行引用管理,源码中Obj_ref变量为被引用次数,当有此对象的引用存在的时候,obj_ref-1, 反之+1,当obj_ref减为0时, 该对象立即被回收,对象占用的内存空间将被释放。引用计数机制所带来的维护引用计数的额外操作与Python运行中所进行的内存分配和释放,引用赋值次数是成正比的,这也是引用计数执行效率比较慢的原因。Python中使用很多内存池来进行不同对象的管理和优化。这里面还会存在循环引用的问题,当若干对象相互引用,会造成每个对象的引用计数都不会为0,这样这些对象所占用的内存永远不会被回收。


双向环状链表-Refchain:Python底层维护了一个双向循环链表RefChain,每当创建一个新对象的时候就会将对象添加到这个链表中。在refChain内部,每个对象都有一个ob_refcnt来记录当前对象被引用的次数

0.png

refChain中Object的定义

define PyObject_HEAD       PyObject ob_base;
#define PyObject_VAR_HEAD      PyVarObject ob_base;
// 宏定义,包含 上一个、下一个,用于构造双向链表用。(放到refchain链表中时,要用到)
#define _PyObject_HEAD_EXTRA            \
    struct _object *_ob_next;           \
    struct _object *_ob_prev;
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;

C语言底层定义的两种结构,一种是PyObject 和PyVarObject。 当创建值类型数据的时候,创建PyObject(ob_next, ob_prev, ob_refcnt, type)4个值。 当创建像list,dict,tuple和set的时候,便创建PyVarObject(ob_next, ob_prev, ob_refcnt, type, ob_size)在值类型数据的基础上添加一个元素个数, 就是5个值。


问题


当你创建了list1, list2。list1中有一个引用时list2, list2中有一个引用是list1。这时候就会发生循环引用的问题,导致最后del两个list了但是他们的引用对象计数在refChain中还是1,永远不会被清楚,内存一直被占据着。


清除标记算法:


目的:为了解决引用计数器循环引用的不足。


实现:在python的底层再维护一个链表,链表中专门放哪些可能存在循环引用的对象(list, tuple, dict, set)


在python内部某种情况下触发,回去扫描可能存在循环应用的链表中的每个元素, 肩差是否有循环引用,如果有则让双方的计数器-1;如果是0则垃圾回收


使用此种方法无法解决循环引用的问题,所以引入标记清楚算法(Mark_Sweep)。GC会对所有对象打上标记,分为活动对象和非活动对象。Python中通过一个directed graph进行对所有对象的标记维护。沿着途中的所有引用,从root object遍历,能到达的对象为活动对象,无法到达的对象为非活动对象。GC首先会把这些标记为非活动对象的object回收

python 通过ref_count计算应用计数垃圾回收,gc_ref是ref_count副本,用于循环应用的垃圾回收,GC时分两步,一是gc_ref自减1,摘除循环引用;二是从root根出发标记是否可达。最后gc_ref为0且不可达将被回收

问题:


1.什么时候扫描


2.可能存在循环引用的链表扫描代价大, 每次扫描耗时久


生命代, 分代回收:


将可能存在循环应用的对象维护成3个链表:


0代: 0代中的对象个数达到700个扫描一次。


1代:0代扫描10次,则1代扫描一次


2代:1代扫描10次, 则2代扫描一次


Python中还会使用分代回收。Python将所有的对象的存活时间分为三个集合,0代(新代),1代(中年代),2代(老年代)。三个代分别指向3个不同的链表,对象的存活时间越长,GC回收对象的频率就越低。新创建的对象都会分配在年轻代,年轻代链表的总数达到上限时,Python垃圾回收机制就会被触发,把那些可以被回收的对象回收掉, 那些不会回收的对象就会被一道中年代去。以此类推,老年代中的对象是存活时间最久的对象,甚至是存活于整个系统的生命周期。当对象数量为700时进行一次年轻代回收,当进行10次年轻代回收后进行一次中年代回收。


Python缓存


池(int, str)


目的:为了避免重复创建和销毁一些常见对象,维护池 var < 257的时候,直接从缓存池中取int。 > 257的时候就会创建一个新的对象,开辟新的内存空间。


free List(float/list/tuple/dict)


当一个对象的引用计数器为0的时候,内部不会直接回收,二十直接将对西昂添加到free_list 链表中当缓存。当再去创建兑现过的时候,不会再重新开辟内存,而是直接使用free_list


常见类型的struct


float 类型

typedef struct {
      PyObject_HEAD
      double ob_fval;
  } PyFloatObject;

int类型

struct _longobject {
      PyObject_VAR_HEAD
      digit ob_digit[1];
  };
  /* Long (arbitrary precision) integer object interface */
  typedef struct _longobject PyLongObject; /* Revealed in longintrepr.h */

str类型

typedef struct {
      PyObject_HEAD
      Py_ssize_t length;          /* Number of code points in the string */
      Py_hash_t hash;             /* Hash value; -1 if not set */
      struct {
          unsigned int interned:2;
          /* Character size:
         - PyUnicode_WCHAR_KIND (0):
           * character type = wchar_t (16 or 32 bits, depending on the
             platform)
         - PyUnicode_1BYTE_KIND (1):
           * character type = Py_UCS1 (8 bits, unsigned)
           * all characters are in the range U+0000-U+00FF (latin1)
           * if ascii is set, all characters are in the range U+0000-U+007F
             (ASCII), otherwise at least one character is in the range
             U+0080-U+00FF
         - PyUnicode_2BYTE_KIND (2):
           * character type = Py_UCS2 (16 bits, unsigned)
           * all characters are in the range U+0000-U+FFFF (BMP)
           * at least one character is in the range U+0100-U+FFFF
         - PyUnicode_4BYTE_KIND (4):
           * character type = Py_UCS4 (32 bits, unsigned)
           * all characters are in the range U+0000-U+10FFFF
           * at least one character is in the range U+10000-U+10FFFF
         */
          unsigned int kind:3;
          unsigned int compact:1;
          unsigned int ascii:1;
          unsigned int ready:1;
          unsigned int :24;
      } state;
      wchar_t *wstr;              /* wchar_t representation (null-terminated) */
  } PyASCIIObject;
  typedef struct {
      PyASCIIObject _base;
      Py_ssize_t utf8_length;     /* Number of bytes in utf8, excluding the
                                   * terminating \0. */
      char *utf8;                 /* UTF-8 representation (null-terminated) */
      Py_ssize_t wstr_length;     /* Number of code points in wstr, possible
                                   * surrogates count as two code points. */
  } PyCompactUnicodeObject;
  typedef struct {
      PyCompactUnicodeObject _base;
      union {
          void *any;
          Py_UCS1 *latin1;
          Py_UCS2 *ucs2;
          Py_UCS4 *ucs4;
      } data;                     /* Canonical, smallest-form Unicode buffer */
  } PyUnicodeObject;

list类型

typedef struct {
      PyObject_VAR_HEAD
      PyObject **ob_item;
      Py_ssize_t allocated;
  } PyListObject;

tuple类型

typedef struct {
      PyObject_VAR_HEAD
      PyObject *ob_item[1];
  } PyTupleObject;

list 类型

typedef struct {
      PyObject_HEAD
      Py_ssize_t ma_used;
      PyDictKeysObject *ma_keys;
      PyObject **ma_values;
  } PyDictObject;
相关文章
|
17天前
|
缓存 监控 算法
Python内存管理:掌握对象的生命周期与垃圾回收机制####
本文深入探讨了Python中的内存管理机制,特别是对象的生命周期和垃圾回收过程。通过理解引用计数、标记-清除及分代收集等核心概念,帮助开发者优化程序性能,避免内存泄漏。 ####
29 3
|
26天前
|
存储 算法 安全
JVM常见面试题(四):垃圾回收
堆区域划分,对象什么时候可以被垃圾器回收,如何定位垃圾——引用计数法、可达性分析算法,JVM垃圾回收算法——标记清除算法、标记整理算法、复制算法、分代回收算法;JVM垃圾回收器——串行、并行、CMS垃圾回收器、G1垃圾回收器;强引用、软引用、弱引用、虚引用
|
2月前
|
存储 监控 算法
美团面试:说说 G1垃圾回收 底层原理?说说你 JVM 调优的过程 ?
尼恩提示: G1垃圾回收 原理非常重要, 是面试的重点, 大家一定要好好掌握
美团面试:说说 G1垃圾回收 底层原理?说说你 JVM 调优的过程  ?
|
5月前
|
存储 缓存 监控
Java面试题:在Java中,对象何时可以被垃圾回收?编程中,如何更好地做好垃圾回收处理?
Java面试题:在Java中,对象何时可以被垃圾回收?编程中,如何更好地做好垃圾回收处理?
76 0
|
2月前
|
机器学习/深度学习 算法 Java
机器学习、基础算法、python常见面试题必知必答系列大全:(面试问题持续更新)
机器学习、基础算法、python常见面试题必知必答系列大全:(面试问题持续更新)
|
2月前
|
Java C++ Python
【面试宝典】深入Python高级:直戳痛点的题目演示(下)
【面试宝典】深入Python高级:直戳痛点的题目演示(下)
|
2月前
|
设计模式 Unix Python
【面试宝典】深入Python高级:直戳痛点的题目演示(上)
【面试宝典】深入Python高级:直戳痛点的题目演示(上)
|
3月前
|
缓存 Java Python
python垃圾回收&缓存机制
python垃圾回收&缓存机制
|
3月前
|
存储 算法 Java
关于python3的一些理解(装饰器、垃圾回收、进程线程协程、全局解释器锁等)
该文章深入探讨了Python3中的多个重要概念,包括装饰器的工作原理、垃圾回收机制、进程与线程的区别及全局解释器锁(GIL)的影响等,并提供了详细的解释与示例代码。
37 0
|
4月前
|
算法 Java 开发者
Python垃圾回收机制
Python垃圾回收机制
下一篇
DataWorks