python中的万物皆对象,python的存储问题是对象的存储问题,并且对每一个对象,python对象会分配一块内存空间去存储它
简单的来说,python的内存管理机制总结为:
1、垃圾回收
2、内存池机制
3、对象缓冲池机制

1、垃圾回收

1.1 引用计数

引用计数是一种非常高效的内存管理机制。当一个python对象被引用时,其引用计数+1;当其不再被一个对象引用时,则计数减1;
当引用计数等于0时对象被删除。

  • 引用计数增加情况:
    • 创建一个对象,例如: a=xx,引用计数1。
    • 赋值b=a,引用计数加1。
    • 浅拷贝、函数传参foo(x)等。
    • 作为容器对象的一个元素,例如,a= [1,x,22] 。
  • 引用计数减少的情况:
    • 一个本地对象离开它的作用域。 比如,上面的foo(x)函数结束时,x指向的对象引用减1
    • 对象的别名被显示的销毁,del x;或者del y
    • 对象的一个别名被赋值给其他对象: x=123
    • 对象从一个窗口对象中移除:carry.remove(x)
    • 窗口对象本身被销毁: del carry, 或者窗口对象本身离开了作用域。

引用计数的优点和缺点
优点: 简单实用:只要引用计数为0,内存直接释放,不受时间限制
缺点: 维护引用计数消耗资源; 容器对象出现循环引用
ps: 出现循环引用怎么办? 标记清除,分代回收

1.2 标记清除

标记清除Mark-Sweep是针对循环引用问题的回收机制,作用的对象是容器类型对象,比如,list、set、dict等

原理: 通过根节点对象(不会被删除的对象),根据引用的方向,把所有活动对象(可遍历的)打上标记,然后回收没有被标记的非活动对象(循环引用 小团体);

『标记清除(Mark—Sweep)』算法是一种基于追踪回收(tracing GC)技术实现的垃圾回收算法。它分为两个阶段:第一阶段是标记阶段,GC 会把所有的『活动对象』打上标记,第二阶段是把那些没有标记的对象『非活动对象』进行回收。具体过程如下:
对象之间通过引用(指针)连在一起,构成一个有向图,对象构成这个有向图的节点,而引用关系构成这个有向图的边。从根对象(root object)出发,沿着有向边遍历对象,可达的(reachable)对象标记为活动对象,不可达的对象就是要被清除的非活动对象。根对象就是全局变量、调用栈、寄存器。

1.3 分代回收

分代回收是建立在标记清除基础上的一种辅助回收容器器对象的 GC 机制。主要原因是每次标记清除的时间成本太高。

无论开发的程序类型如何,规模如何,都有这样的相同之处:一些比例的内存生存周期都很短,而另一些内存的生存周期比较长,可能会伴随着整个程序的开始和结束。

所以分代回收就根据系统中内存存活时间把它们划分成不同的集合:
一共分成三个集合,每个集合称为一个代。
第一代未被回收就会进入第二代,同理第二代未被回收就会进入第三代,它们的垃圾收集频率随对象存活时间的增大而减小。
也就是说:对于存活时间越长的对象,就越不可能是垃圾,减少对其的收集频率。而新创建的对象都在第一代,第一代集合总数达到上限后,会触发 GC 机制:可以回收的对象所占的内存被释放,不能被回收的移到下一代

2、内存池机制

python在运行期间会大量执行malloc和free操作,频繁在用户态和核心态之间进行切换,这将严重影响python的执行效率。
为了加速python的执行效率,Python引入了一个内存池机制,用于管理对小块内存的内存和释放

内存池是预先从内存中申请的内存块,当创建小于256字节的对象时,从内存池找那个申请内存空间;大于256字节是对象直接从内存中申请空间。释放内存时,来自内存池空间的返回内存池, 这样减少了内存碎片,提升了效率。
通过修改 Python 源代码,我们可以改变这个默认值(256K),从而改变 Python 的默认内存管理行为。

调优手段(了解)
1、手动垃圾回收
2、调高垃圾回收阈值
3、避免循环引用(手动解循环引用和使用弱引用)

3、对象缓冲池机制

对于固定范围整数和短小的字符整,python会执行缓存机制,即将这些对象进行缓存,不会为想同的对象分配多个内存空间
例如,对于 [-5,257) 这样的小整数,系统已经初始化好,可以直接拿来用
虽然整数是不可变类型,但是a,b的内存地址明显相同

__END__