内存管理

Python有一个私有堆空间来保存所有的对象和数据结构。作为开发者,我们无法访问它,是解释器在管理它。但是有了核心API后,我们可以访问一些工具。Python内存管理器控制内存分配。
另外,内置垃圾回收器会回收使用所有的未使用内存,所以使其适用于堆空间。

引用计数

Python采用了类似Windows内核对象一样的方式来对内存进行管理。每一个对象,都维护这一个对指向该对对象的引用的计数。当变量被绑定在一个对象上的时候,该变量的引用计数就是1,(还有另外一些情况也会导致变量引用计数的增加),系统会自动维护这些标签,并定时扫描,当某标签的引用计数变为0的时候,该对就会被回收。

垃圾回收

python不像C++,Java等语言一样,他们可以不用事先声明变量类型而直接对变量进行赋值。对Python语言来讲,对象的类型和内存都是在运行时确定的。这也是为什么我们称Python语言为动态类型的原因(这里我们把动态类型可以简单的归结为对变量内存地址的分配是在运行时自动判断变量类型并对变量进行赋值)。

分代回收

  1. 分代回收是建立在标记清除技术基础之上的,是一种以空间换时间的操作方式。
  2. Python将内存分为了3“代”,分别为年轻代(第0代)、中年代(第1代)、老年代(第2代)
  3. 他们对应的是3个链表,它们的垃圾收集频率与对象的存活时间的增大而减小。
  4. 新创建的对象都会分配在年轻代,年轻代链表的总数达到上限时,Python垃圾收集机制就会被触发
  5. 把那些可以被回收的对象回收掉,而那些不会回收的对象就会被移到中年代去,依此类推
  6. 老年代中的对象是存活时间最久的对象,甚至是存活于整个系统的生命周期内。

对象存储

在Python中万物皆对象

不存在基本数据类型,0, 1.2, True, False, "abc"等,这些全都是对象

所有对象, 都会在内存中开辟一块空间进行存储

2.1 会根据不同的类型以及内容, 开辟不同的空间大小进行存储 2.2 返回该空间的地址给外界接收(称为”引用”), 用于后续对这个对象的操作 2.3 可通过 id() 函数获取内存地址(10进制) 2.4 通过 hex() 函数可以查看对应的16进制地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Person:
pass

p = Person()
print(p)
print(id(p))
print(hex(id(p)))


>>>> 打印结果

<__main__.Person object at 0x107030470>
4412605552
0x107030470

对于整数和短小的字符, Python会进行缓存; 不会创建多个相同对象

此时, 被多次赋值, 只会有多份引用

1
2
3
4
5
6
7
num1 = 2
num2 = 2
print(id(num1), id(num2))

>>>> 打印结果

4366584464 4366584464

容器对象, 存储的其他对象, 仅仅是其他对象的引用, 并不是其他对象本身

4.1 比如字典, 列表, 元组这些”容器对象” 4.2 全局变量是由一个大字典进行引用 4.3 可通过 global() 查看

2 对象回收 2.1 引用计数器 2.1.1概念

一个对象, 会记录着自身被引用的个数 每增加一个引用, 这个对象的引用计数会自动+1 每减少一个引用, 这个对象的引用计数会自动-1

引用计数+1场景

1
2
3
4
5
6
7
8
9
1、对象被创建
p1 = Person()
2、对象被引用
p2 = p1
3、对象被作为参数,传入到一个函数中
log(p1)
这里注意会+2, 因为内部有两个属性引用着这个参数
4、对象作为一个元素,存储在容器中
l = [p1]

引用计数-1场景

1
2
3
4
5
6
7
8
1、对象的别名被显式销毁
del p1
2、对象的别名被赋予新的对象
p1 = 123
3、一个对象离开它的作用域
一个函数执行完毕时
内部的局部变量关联的对象, 它的引用计数就会-1
4、对象所在的容器被销毁,或从容器中删除对象

查看引用计数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import sys

class Person:
pass

p1 = Person() # 1

print(sys.getrefcount(p1)) # 2

p2 = p1 # 2

print(sys.getrefcount(p1)) # 3

del p2 # 1
print(sys.getrefcount(p1)) # 2

del p1
# print(sys.getrefcount(p1)) #error,因为上一行代码执行类p1对象已经销毁

>>>> 打印结果

2
3
2

循环引用

1
2
3
4
5
6
7
8
9
10
11
12
# 循环引用
class Person:
pass

class Dog:
pass

p = Person()
d = Dog()

p.pet = d
d.master = p

对象间互相引用,导致对象不能通过引用计数器进行销毁

手动触发垃圾回收,挥手循环引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import objgraph
import gc

class Person:
pass

class Dog:
pass

p = Person()
d = Dog()

p.pet = d
d.master = p


del p
del d

gc.collect() #手动触发垃圾回收

print(objgraph.count("Person"))
print(objgraph.count("Dog"))

>>>> 打印结果
0
0