1.计算机中将内存分为3部分,一部分是堆,一部分是栈,还有静态存储区。
- 静态存储区:存放全局变量,静态变量,由系统自动开辟内存,自动回收,生存周期为整个程序
- 栈一般存放局部变量,形参等,由系统自动开辟空间,自动回收。其生存周期为块内,地址为由高到低分配
- 堆一般是由程序员自己申请,自己回收,如果忘记回收就会一直占用内存,电脑速度就会变慢。且不断的malloc不回收的话会导致内存碎片话,可用的空间越来越少。用专业的词汇就叫内存泄露。
堆一般是由程序员自己申请,自己回收,如果忘记回收就会一直占用内存,电脑速度就会变慢。且不断的malloc不回收的话会导致内存碎片话,可用的空间越来越少。用专业的词汇就叫内存泄露。
2.不同的语言都有一定额机制来解决,今天就来看看python是如何解决的。
- 首先先来看下2个重要的结构体PyObject()和PyVarObject()
1.typedef struct _object { 2._PyObject_HEAD_EXTRA // 用于构造双向链表 3.Py_ssize_t ob_refcnt; // 引用计数器 4.struct _typeobject *ob_type; // 数据类型 5.} PyObject; 1.typedef struct { 2.PyObject ob_base; // PyObject对象 3.Py_ssize_t ob_size; /* Number of items in variable part,即:元素个数 */ 4.} PyVarObject;
第一个结构有先驱,后继指针,引用计数器,数据类型4个基本结构,int,float,string等类型都是基于该结构体,而list,tuple,dir,set由多个元素的结构体都是基于第二个结构体。了解了基本的结构体,我们在来聊聊python是如何创建变量的,python底层由4条链表,这4张链表解决了垃圾回收的所有问题。
了解了基本的结构体,我们在来聊聊python是如何创建变量的,python底层由4条链表,这4张链表解决了垃圾回收的所有问题。第一张表:双线循环链表。,每创建一个新的变量,都会将其放到该链表中。同时,引用计数器refcnt加一。每引用一次,计数器就会加一。
import os
import sys
a=12
b=a
print(sys.getrefcount(12))
c=b
print(sys.getrefcount(12))
print(id(a),id(b))
1.计数器回收机制
每引用一次计数器加1,每del一个,计数器减一,当计数器为0时,变成垃圾被回收。
2.标记回收机制
计数器回收机制存在一个bug,当有循环引用时变会成为永久垃圾例如:
l1=[1,2,3]
l2=[2,3,4]
l1.append((l2))
l2.append((l1))
del l1
del l2
当d1,d2被删除时,便失去了对列表的调用,但列表的引用又不为0不会被当作垃圾回收,所以变成了永久垃圾。为了解决这一问题,出现了标记清除。另外开辟一个特殊链表,专门用于存放可能存在循环引用的对象,例如list,tuple,set,dir.每隔一段时间就扫描一次链表。发现循环引用就将双方的引用标记变为-1.
3.分代清除
这是在标记清除的链表分了3个链表,0代,1代,2代。每新建一个对象将其加入到链表中,当链表满700次时进行扫描,但通常会查看1代是否达到阈值,没有达到阈值扫描0代,将循环引用删除。当1代达到阈值会扫描2代,2代如果也达到阈值,进行全盘扫描,并将计数器归0.
上面4条链表解决了python关于垃圾回收的所有问题。当然,为了提高效率,python也有一定的缓存机制。
- 常量池:-5~257.
定义一个常量时,会先检查是否在常量池内,如果在,直接引用,超出范围在新建一个对象
2.Float
Python有一个free_list来存放float,大约有80个,当计数器为0时,不会直接清除,而是放入到链表中,下次在创建新对象时,不在开辟新的空间,直接从选取表中的一个对象对其重新赋值。
3.Tuple
List,set,dir都与float大同小异。Tuple则不同,其free_list有20个,每个存放的并不是值,而是个数,第一个存放长度为0的元组个数,第2个存放长度为1的元组个数,以此类推,第20个存放长度为0 的元组。每个都可以存放2000