Python:Python进阶:内存管理机制

Python内存管理机制

  • 1. 堆
  • 2. 栈
  • 3. 引用
  • 4. Python中可变对象和不可变对象
  • 有个问题:你可以好好思考下
  • 总结

Python内存管理程序是用 C/C++写的,这里我们以 CPython解释器为例说明。
在Python 中 所有数据类型 包括:int dict str都是一个对象,叫做 PyObject 。

Python:Python进阶:内存管理机制_第1张图片

下面,我主要从 堆栈 ,引用这三个方面来浅析 CPython 内存管理机制

1. 堆

主要负责存储 CPython运行时的所有对象实体 (也就是 Python对象的所有属性数据),例如:
smt = 'Hello, world ’ 是一个 PyASCIIObject
n = 23 是一个 整数 PyLongObject

  1. 上面两个例子,他们都是 Python对象。
  2. 赋值符号 “=” 右边的是 数据值,CPython 会将其存储到 堆内存中。

2. 栈

在CPython的语义中,又叫数据栈或值栈,它主要负责保存对堆中 Python对象的引用。
smt = ‘Hello, World’ , CPython会将 ‘Hello,World’ 这个字符串实体所处的内存地址压入栈中, 而不是将 ‘Hello,world’ 实体压入栈中。
smt 仅仅 持有对该Python对象实体的引用 。

3. 引用

我们先看这个示例

s1 = 'hello world'
s2 = 'hello world'
print(id(s1))  # 2028548222256
print(id(s2))  # 2028548222256
print(s1==s2) # True
  1. 示例中表明:变量s1和s2 持有Python 对象在堆内存相同的地址。这个可以从打印出的 引用的内存地址和 Python 判断两个对象是否相等(==) 都可以看出来。
  2. 现在我们来,分析下上面引用地址相同原因
  3. 变量 s1持有Python对象 ‘Hello world’ 引用,对于CPython虚拟机来说:在 执行 s1 = ‘Hello world’ 时,将内存地址 0x71334 压入内存栈。
  4. 当 CPython碰到同样的 语句 s2 = ‘Hello world’ ,由于指向同一个PythonObject对象,那么 s2 和 s1一样,自然也会持有 ‘Hello world’引用,即 s2 实质上也拥有 ‘Hello world’ 堆中的地址。
    Python:Python进阶:内存管理机制_第2张图片

4. Python中可变对象和不可变对象

我们已 列表对象作为研究主题

L = [11,12,32,45]
print("1.打印列表中的元素所在地址")
for X in L:
    print(id(X))

print("2.打印列表对象L的内存地址:",id(L))
print("3.修改第三个元素: 734")
L[2] = 734

print("4.再次打印列表中元素的内存地址")
for X in L:
    print(id(X))
    
print("5.打印列表对象L的内存地址: ",id(L))

# 打印运行结果
1.打印列表中的元素所在地址
2655576719920
2655576719952
*2655576720592*
2655576721008
2.打印列表对象L的内存地址: 2656002024640
3.修改第三个元素
4.再次打印列表中元素的内存地址
2655576719920
2655576719952
*2655577763760*
2655576721008
5.打印列表对象L的内存地址:  2656002024640

根据上述代码和运行结果可知如下事实:

  • list 类型的 L 本身 是一个 Python对象,其对象实体就是在 堆内存中。
  • list 类型的对象,作为一个容器级别的对象,其列表存储的是元素实体 的引用,而非 元素实体本身。
  • 对 list 对象中的某个元素修改的本质 是 :让被修改元素指向其他元素的引用。修改该元素时,实际上CPython 在堆内存中创建一个新的对象来分配新的内存空间,并且保存该新增的对象(整数734),所以 list对象的第三个元素 L(2) 不再保存对 整数32 引用,更新为对 734 的引用。
  • 但是list类型对象在其元素修改前后,变量L对象 始终引用同一个 list对象。
    Python:Python进阶:内存管理机制_第3张图片
    从上面的例子我们也可以知道
  1. 可变对象:是指可以变更此对象,即可以对此对象做增删改后,不会开辟新内存地址:如 List array.array bytearray 和memoryview
  2. 不可变对象:上面示例中list 的元素都是不可变对象,Python中原始数据类型,如:数字类型(int, float) 字符串(str) 字节数字(bytes),tuple (元组)
  3. 可变对象和不可变对象

有个问题:你可以好好思考下

我们知道,整数类型是右值,理论上应该返回具体值,但是为什么能返回内存地址了 ?

回答这个问题之前,我们需要知道:在Python中,一切事物都是对象,不论是整数,字符串,甚至是其他容器级别的数据类型,都是由CPython的C 底层由一个叫 struct PyObject 结构体所封装。

  1. PyObject的结构体在 CPython运行时存储在堆中,对于C底层来说,任意的PyObject结构体 返回的是内存地址,因此它是一个左值
  2. 但是对于 Python语义来说,不存在 静态语言汇总的左值和右值,它只能理解的是 PyObject 这个C实现的对象。

总结

我们理解了CPython的基本内存模型后,但要说的是,这是一个简化的内存模型, CPython虚拟机对于堆内存管理有一套较为复杂的内存池管理方案。通过上面的分析,我们知道了 Python内存管理的两个基本概念

  1. 什么是 CPython的栈引用
  2. 什么是 CPython对象的引用
    我们从堆内存的角度理解为什么 CPython要对 Python对象分类 可变对象和不可变对象,这是因为 Python 变量持有 Python对象的引用(从C的角度就是:Python变量持有PyObject对象的指针)去访问 Python 对象实体本身,比持有 Python 对象实体的副本 更高效,更节省堆和栈内存开销
  3. 当 多个Python变量 引用同一个 Python对象就 会涉及到 引用计数器,引用计数器属于内存垃圾回收的范畴,由引用计数又会 牵扯出 CPython一个致命 的诟病,GIL(全局解释器锁),为什么这么多年CPython不能去掉 GIL ,很大原因就是引用计数器有关。

你可能感兴趣的:(Python,python,内存管理机制)