Python万字深入内存管理讲解

目录
  • Python内存管理
  • 一、对象池
    • 1.小整数池
    • 2.大整数池
    • 3.inter机制(短字符串池)
  • 二、垃圾回收
    • 2.1.引用计数
      • 2.1.1 引用计数增加
      • 2.1.2 引用计数减少
    • 2.2.标记清除
    • 2.3.分代回收
      • 2.3.1 分代回收触发时机?(GC阈值)
      • 2.3.2 查看引用计数(gc模块的使用)
  • 三、怎么优化内存管理
    • 1.手动垃圾回收
    • 2.调高垃圾回收阈值
    • 3.避免循环引用
  • 四、总结

Python内存管理

一、对象池

1.小整数池

系统默认创建好的,等着你使用

概述:

整数在程序中的使用非常广泛,Python为了优化速度,使用了小整数对象池,避免为整数频繁申请和销毁内存空间。

Python 对小整数的定义是 [-5, 256] ,这些整数对象是提前建立好的,不会被垃圾回收。

在一个 Python 的程序中,无论这个整数处于LEGB(局部变量,闭包,全局,内建模块)中的哪个位置,所有位于这个范围内的整数使用的都是同一个对象。

# 交互式环境下:
>>> a = 100
>>> b = 100
>>> id(a)
140720433537792
>>> id(b)
140720433537792
>>> a is b
True
>>> 

我们可以看出a,b指向同一个内存地址。

2.大整数池

大整数池:默认创建出来,池内为空的,创建一个就会往池中存储一个

# python交互式环境
>>> a = 257
>>> b = 257
>>> id(a)
2085029722896
>>> id(b)
2085029722960
>>> a is b
False
>>> 

a , b 不是指向同一个内存地址。

python中对大于256的整数,会重新分配对象空间地址保存对象。

3.inter机制(短字符串池)

每个单词(字符串),不夹杂空格或者其他符号,默认开启intern机制,共享内存,靠引用计数决定是否销毁。

>>> s1 = 'hello'
>>> s2 = 'hello'
>>> id(s1)
2178093449264
>>> id(s2)
2178093449264
>>> s1 is s2
True
>>> 

字符串s1和s2中没有空格时,可以看出,这里的s1与s2指向同一个内存地址。

当我们在he和llo之间加一个空格

>>> s1 = 'he llo'
>>> s2 = 'he llo'
>>> id(s1)
2278732636592
>>> id(s2)
2278732636528
>>> s1 is s2
False
>>> 

这时的字符串s1和s2就没有指向同一个内存地址。

对于字符串来说,如果不包含空格的字符串,则不会重新分配对象空间,对于包含空格的字符串则会重新分配对象空间。

二、垃圾回收

概述:

python采用的是引用计数机制为主,隔代回收和标记清除机制为辅的策略

概述:
现在的高级语言如java,c\# 等,都采用了垃圾收集机制,而不再是c,c++里用户自己管理维护内存的方式。

自己管理 内存极其自由, 可以任意申请内存,但如同一把双刃剑,为大量内存泄露,悬空指针等bug埋下隐患。

python里也同java一样采用了垃圾收集机制,不过不一样的是: 
python采用的是引用计数机制为主,隔代回收机制为辅的策略

2.1.引用计数

在Python中,每个对象都有指向该对象的引用总数---引用计数

查看对象的引用计数:sys.getrefcount()

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

2.1.1 引用计数增加

a、对象被创建

b、另外变量也指向当前对象

c、作为容器对象的一个元素

d、作为参数提供给函数:test(x)

2.1.2 引用计数减少

a、变量被显式的销毁

b、对象的另外一个变量重新赋值

c、从容器中移除

d、函数被执行完毕

看代码:

# -*- coding: utf-8 -*-
import sys
class Test(object):
    def __init__(self):
        print('当前对象已经被创建,占用的内存地址为:%s' % hex(id(self)))
a = Test()
print('当前对象的引用计数为:%s' % sys.getrefcount(a))  # 2
b = a
print('当前对象的引用计数为:%s' % sys.getrefcount(a))  # 3
list1 = []
list1.append(a)
print('当前对象的引用计数为:%s' % sys.getrefcount(a))  # 4
del b
print('当前对象的引用计数为:%s' % sys.getrefcount(a))  # 3
list1.remove(a)
print('当前对象的引用计数为:%s' % sys.getrefcount(a))  # 2
del a
print('当前对象的引用计数为:%s' % sys.getrefcount(a))  # 报错
'''
Traceback (most recent call last):
  File "E:/Python Project/Python 高级编程/内存管理/垃圾收集.py", line 30, in <module>
    print('当前对象的引用计数为:%s' % sys.getrefcount(a))
NameError: name 'a' is not defined
'''

当Python的某个对象的引用计数降为0时,说明没有任何引用指向该对象,该对象就成为要被回收的垃圾。比如某个新建对象,被分配给某个引用,对象的引用计数变为1。如 为0,那么该对象就可以被垃圾回收。

2.2.标记清除

标记清除(Mark—Sweep)算法是一种基于追踪回收(tracing GC)技术实现的垃圾回收算法。

它分为两个阶段:

第一阶段是标记阶段,GC会把所有的活动对象打上标记

第二阶段是把那些没有标记的对象非活动对象进行回收。

对象之间通过引用(指针)连在一起,构成一个有向图,对象构成这个有向图的节点,而引用关系构成这个有向图的边。从根对象(root object)出发,沿着有向边遍历对象,可达的(reachable)对象标记为活动对象,不可达的对象就是要被清除的非活动对象。根对象就是全局变量、调用栈、寄存器。

>

在上图中,可以从程序变量直接访问块1,并且可以间接访问块2和3。程序无法访问块4和5。第一步将标记块1,并记住块2和3以供稍后处理。第二步将标记块2,第三步将标记块3,但不记得块2,因为它已被标记。扫描阶段将忽略块1,2和3,因为它们已被标记,但会回收块4和5。

标记清除算法作为Python的辅助垃圾收集技术,主要处理的是一些容器对象,比如list、dict、tuple等,因为对于字符串、数值对象是不可能造成循环引用问题。Python使用一个双向链表将这些容器对象组织起来。不过,这种简单粗暴的标记清除算法也有明显的缺点:清除非活动的对象前它必须顺序扫描整个堆内存,哪怕只剩下小部分活动对象也要扫描所有对象。

2.3.分代回收

因为, 标记和清除的过程效率不高。清除非活动的对象前它必须顺序扫描整个堆内存,哪怕只剩下小部分活动对象也要扫描所有对象。还有一个问题就是:什么时候扫描去检测循环引用?

为了解决上述的问题,python又引入了分代回收。分代回收解决了标记清楚时什么时候扫描的问题,并且将扫描的对象分成了3级,以及降低扫描的工作量,提高效率。

  • 0代: 0代中对象个数达到700个,扫描一次。
  • 1代: 0代扫描10次,则1代扫描1次。
  • 2代: 1代扫描10次,则2代扫描1次。

隔代回收是用来解决交叉引用(循环引用),并增加数据回收的效率. 原理:通过对象存在的时间不同,采用不同的算法来 回收垃圾.

形象的比喻, 三个链表,零代链表上的对象(新创建的对象都加入到零代链表),引用数都是一,每增加一个指针,引用加一,随后 python会检测列表中的互相引用的对象,根据规则减掉其引用计数.

GC算法对链表一的引用减一,引用为0的,清除,不为0的到链表二,链表二也执行GC算法,链表三一样. 存在时间越长的 数据,越是有用的数据

2.3.1 分代回收触发时机?(GC阈值)

随着你的程序运行,Python解释器保持对新创建的对象,以及因为引用计数为零而被释放掉的对象的追踪。

从理论上说,这两个值应该保持一致,因为程序新建的每个对象都应该最终被释放掉。当然,事实并非如此。因为循环 引用的原因,从而被分配对象的计数值与被释放对象的计数值之间的差异在逐渐增长。一旦这个差异累计超过某个阈 值,则Python的收集机制就启动了,并且触发上边所说到的零代算法,释放“浮动的垃圾”,并且将剩下的对象移动到 一代列表。

随着时间的推移,程序所使用的对象逐渐从零代列表移动到一代列表。而Python对于一代列表中对象的处理遵循同样的 方法,一旦被分配计数值 与被释放计数值累计到达一定阈值,Python会将剩下的活跃对象移动到二代列表。

通过这种方法,你的代码所长期使用 的对象,那些你的代码持续访问的活跃对象,会从零代链表转移到一代再转移到二代。

通过不同的阈值设置,Python可 以在不同的时间间隔处理这些对象。

Python处理零代最为频繁,其次是一代然后才是二代。

2.3.2 查看引用计数(gc模块的使用)

# 引入gc模块
import gc
# 常用函数:
gc.get_count()
# 获取当前自动执行垃圾回收的计数器,返回一个长度为3的列表
gc.get_threshold()
# 获取gc模块中自动执行垃圾回收的频率
gc.set_threshold(threshold0[,threshold1,threshold2])
# 设置自动执行垃圾回收的频率
gc.disable()
# python3默认开启gc机制,可以使用该方法手动关闭gc机制
gc.collect()
# 手动调用垃圾回收机制回收垃圾

内存管理是使用计算机必不可少的一部分,无论好坏,Python几乎会在后台处理一切内存管理的问题。Python抽象出许多使用计算机的严格细节,这让我们在更高层次进行开发,而不用担心所有字节的存储方式和位置。

# -*- coding: utf-8 -*-
import gc
import sys
import time
class Test(object):
    def __init__(self):
        print('当前对象已经被创建,占用的内存地址为:%s' % hex(id(self)))
    def __del__(self):
        print('当前对象马上被系统GC回收')
# gc.disable()  # 不启用GC,在python3中默认启用
while True:
    a = Test()
    b = Test()
    a.pro = b  # a 和 b之间相互引用
    b.pro = a
    del a
    del b
    print(gc.get_threshold())  # 打印隔代回收的阈值
    print(gc.get_count())  # 打印GC需要回收的对象
    time.sleep(0.2)  # 休眠0.2秒方便查看

终端输出:

三、怎么优化内存管理

1.手动垃圾回收

先调用del a ; 再调用gc.collect()即可手动启动GC

2.调高垃圾回收阈值

gc.set_threshold 设置垃圾回收阈值(收集频率)。

将 threshold 设为零会禁用回收。

3.避免循环引用

四、总结

python采用的是引用计数机制为主,标记-清除和分代回收(隔代回收)两种机制为辅的策略

到此这篇关于Python万字深入内存管理讲解的文章就介绍到这了,更多相关Python内存管理内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Python内存管理器如何实现池化技术

    目录 前言 内存层次结构 内存管理逻辑 内存布局及对应的数据结构 内存分配 内存释放 总结 前言 Python 中一切皆对象,这些对象的内存都是在运行时动态地在堆中进行分配的,就连 Python 虚拟机使用的栈也是在堆上模拟的.既然一切皆对象,那么在 Python 程序运行过程中对象的创建和释放就很频繁了,而每次都用 malloc() 和 free() 去向操作系统申请内存或释放内存就会对性能造成影响,毕竟这些函数最终都要发生系统调用引起上下文的切换.下面我们就来看看 Python 中的内存管理

  • 简单了解python的内存管理机制

    Python引入了一个机制:引用计数. 引用计数 python内部使用引用计数,来保持追踪内存中的对象,Python内部记录了对象有多少个引用,即引用计数,当对象被创建时就创建了一个引用计数,当对象不再需要时,这个对象的引用计数为0时,它被垃圾回收. 总结一下对象会在一下情况下引用计数加1: 1.对象被创建:x=4 2.另外的别人被创建:y=x 3.被作为参数传递给函数:foo(x) 4.作为容器对象的一个元素:a=[1,x,'33'] 引用计数减少情况 1.一个本地引用离开了它的作用域.比如上

  • Python中的内存管理之python list内存使用详解

    前言 使用 Python 的时候,我们知道 list 是一个长度可变对的数组, 可以通过 insert,append 和 extend 轻易的拓展其中的元素个数. 也可以使用运算符 如: [1] + [2] 生成新的数组[1, 2] extend(). "+"."+=" 的区别 "+"将两个 list 相加,会返回到一个新的 list 对象 append 在原 list 上进行修改,没有返回值 从以下代码可以看到, 调用 b = b + [3,

  • python 怎样进行内存管理

    从三个方面来说,主要有方面的措施:对象的引用计数机制.垃圾回收机制.内存池机制. 一.对象的引用计数机制 Python内部使用引用计数,来保持追踪内存中的对象,所有对象都有引用计数. 引用计数增加的情况: 1.一个对象分配一个新名称 2.将其放入一个容器中(如列表.元组或字典) 引用计数减少的情况: 1.使用del语句对对象别名显示的销毁 2.引用超出作用域或被重新赋值 sys.getrefcount( )函数可以获得对象的当前引用计数 多数情况下,引用计数比你猜测得要大得多.对于不可变数据(如

  • python内存管理分析

    本文较为详细的分析了python内存管理机制.分享给大家供大家参考.具体分析如下: 内存管理,对于Python这样的动态语言,是至关重要的一部分,它在很大程度上甚至决定了Python的执行效率,因为在Python的运行中,会创建和销毁大量的对象,这些都涉及到内存的管理. 小块空间的内存池 在Python中,许多时候申请的内存都是小块的内存,这些小块内存在申请后,很快又会被释放,由于这些内存的申请并不是为了创建对象,所以并没有对象一级的内存池机制. Python内存池全景 这就意味着Python在

  • python内存管理机制原理详解

    python内存管理机制: 引用计数 垃圾回收 内存池 1. 引用计数 当一个python对象被引用时 其引用计数增加 1 ; 当其不再被变量引用时 引用计数减 1 ; 当对象引用计数等于 0 时, 对象被删除(引用计数是一种非常高效的内存管理机制) 2. 垃圾回收 垃圾回收机制: ① 引用计数 , ②标记清除 , ③分带回收 引用计数 : 引用计数也是一种垃圾收集机制, 而且也是一种最直观, 最简单的垃圾收集技术.当python某个对象的引用计数降为 0 时, 说明没有任何引用指向该对象, 该

  • Python超详细讲解内存管理机制

    目录 什么是内存管理机制 一.引用计数机制 二.数据池和缓存 什么是内存管理机制 python中创建的对象的时候,首先会去申请内存地址,然后对对象进行初始化,所有对象都会维护在一 个叫做refchain的双向循环链表中,每个数据都保存如下信息: 1. 链表中数据前后数据的指针 2. 数据的类型 3. 数据值 4. 数据的引用计数 5. 数据的长度(list,dict..) 一.引用计数机制 引用计数增加: 1.1 对象被创建 1.2 对象被别的变量引用(另外起了个名字) 1.3 对象被作为元素,

  • Python 内存管理机制全面分析

    内存管理: 概述 在Python中,内存管理涉及到一个包含所有Python对象和数据结构的私有堆(heap). 这个私有堆的管理由内部的Python内存管理器保证.Python内存管理器有不同的组件来处理各种动态存储管理方面的问题,如共享,分割,预分配或缓存. 在最底层,一个原始内存分配器通过与操作系统的内存管理器交互,确保私有堆有足够的空间来存储所有与Python相关的数据.在原始内存分配器的基础上,几个对象特定的分配器在同一个堆上运行,并根据每种对象类型的特点实现不同的内存管理策略.例如,整

  • Python内存管理实例分析

    本文实例讲述了Python内存管理.分享给大家供大家参考,具体如下: a = 1 a是引用,1是对象.Python缓存整数和短字符串,对象只有一份,但长字符串和其他对象(列表字典)则有很多对象(赋值语句创建新的对象). from sys import getrefcount a=[1,2,3] print(getfrecount(a)) 返回4,当使用某个引用作为参数传给getfrecount时,创建了临时引用,+1. 对象引用对象 class from_obj(object): def __i

  • Python万字深入内存管理讲解

    目录 Python内存管理 一.对象池 1.小整数池 2.大整数池 3.inter机制(短字符串池) 二.垃圾回收 2.1.引用计数 2.1.1 引用计数增加 2.1.2 引用计数减少 2.2.标记清除 2.3.分代回收 2.3.1 分代回收触发时机?(GC阈值) 2.3.2 查看引用计数(gc模块的使用) 三.怎么优化内存管理 1.手动垃圾回收 2.调高垃圾回收阈值 3.避免循环引用 四.总结 Python内存管理 一.对象池 1.小整数池 系统默认创建好的,等着你使用 概述: 整数在程序中的

  • python的内存管理和垃圾回收机制详解

    简单来说python的内存管理机制有三种 1)引用计数 2)垃圾回收 3)内存池 接下来我们来详细讲解这三种管理机制 1,引用计数: 引用计数是一种非常高效的内存管理手段,当一个pyhton对象被引用时其引用计数增加1,当其不再被引用时引用计数减1,当引用计数等于0的时候,对象就被删除了. 2,垃圾回收(这是一个很重要知识点): ①  引用计数 引用计数也是一种垃圾回收机制,而且是一种最直观,最简单的垃圾回收技术. 在Python中每一个对象的核心就是一个结构体PyObject,它的内部有一个引

  • C语言详细分析讲解内存管理malloc realloc free calloc函数的使用

    目录 C语言内存管理 一.动态空间申请 二.动态空间的扩容 三.释放内存 C语言内存管理 malloc && realloc && free && calloc c语言中为了进行动态内存管理,<stdlib.h>中提供了几个函数帮助进行内存管理. 我们知道,C语言中是没有C++中的容器或者说是python中list,set这些高级的数据结构的,我们一旦申请了一段内存空间以后这一段空间就归你了,比如我们举个例子,我们申请一个数组 int nums[

  • Python深入学习之内存管理

    语言的内存管理是语言设计的一个重要方面.它是决定语言性能的重要因素.无论是C语言的手工管理,还是Java的垃圾回收,都成为语言最重要的特征.这里以Python语言为例子,说明一门动态类型的.面向对象的语言的内存管理方式.  对象的内存使用 赋值语句是语言最常见的功能了.但即使是最简单的赋值语句,也可以很有内涵.Python的赋值语句就很值得研究. a = 1 整数1为一个对象.而a是一个引用.利用赋值语句,引用a指向对象1.Python是动态类型的语言(参考动态类型),对象与引用分离.Pytho

随机推荐