3. 垃圾回收机制

1. 什么是 “垃圾” ?

  x = 1 //把 “1” 这个值赋给 “x”,在计算机中,先是定义了一个变量x,然后在内存里面开辟了一块内存空间,用来存放x的值,也就是1,这个时候,x和1是绑定的。

  这种绑定关系,叫做 “引用计数”。,这时候,“1”的引用计数就是1.

  如果在这个时候,再一次给x赋值,x = 2,那么这个时候,计算机就重新开辟了一块内存空间,用来存放x的值,也就是2,存放了2之后,x和之前的1就自动解绑了。

  这个时候,1的引用计数就变成0了,因为没有被引用,无法被访问到,计算机就把它视为“垃圾”。

2. 什么是垃圾回收机制

垃圾回收机制(Garbage Collection,简称 “GC”)是python解释器自带的一种机制,专门用来回收不可用变量值所占用的内存空间

Python的垃圾回收机制主要采用的是引用计数为主、标记清除与隔代回收为辅的垃圾回收策略。

3. 为什么要用垃圾回收机制?

大多数程序在运行过程中会申请大量的内存空间,对于一些无用的内存空间如果不及时清理的话会导致内存使用殆尽(内存溢出),导致程序奔溃。

因此内存管理是一件非常重要且繁琐的事情,而垃圾回收机制能够把程序猿从繁琐的内存管理中解放出来。

4. 垃圾回收机制的原理

4.1 引用计数

x = 10  # 直接引用
print(id(x))
y = x
z = x

l = ['a.txt', 'b', x]  # 间接引用
print(id(l[2]))  

d = {'mmm': x}  # 间接引用

print(id(d['mmm']))


x=10
l=['a.txt','b',x] # l=['a.txt'的内存地址,'b'的内存地址,10的内存地址]
x=123
print(l[2])    # 此时 x 的值变为 123 ,但l里x的值依然是10

直接引用

间接引用

引用计数减少

//值18的引用计数一旦变为0,其占用的内存地址就应该被解释器的垃圾回收机制回收

4.1 标记清除

4.1.1 循环引用——>导致内存泄漏问题

l1=[111,] #此时l1被引用一次,引用计数为1
l2=[222,] #此时l2被引用一次,引用计数为1

l1.append(l2) # l1=[值111的内存地址,l2列表的内存地址]#此时l2又被引用一次,引用计数为2
l2.append(l1) # l2=[值222的内存地址,l1列表的内存地址]#此时l1又被引用一次,引用计数为2

print(id(l1[1])) #l1引用l2
print(id(l2))

print(id(l2[1])) #l2引用l1
print(id(l1))

print(l2)
print(l1[1])
#此时,l1和l2互相引用

del l1 #l1引用次数-1
del l2 #l2引用次数-1
#此时直接引用解除关系,但间接引用还在循环引用,引用计数为1没有为0,但永远取不到值

此时两个列表的引用计数均不为0,但两个列表不再被任何其他对象关联,没有任何人可以再引用到它们

所以它俩占用内存空间应该被回收,但由于相互引用的存在,每一个对象的引用计数都不为0,因此这些对象所占用的内存永远不会被释放,所以循环引用是致命的,这与手动进行内存管理所产生的内存泄露毫无区别

所以Python引入了“标记-清除” 与“分代回收”来分别解决引用计数的循环引用与效率低的问题

4.1.2 标记清除

堆区与栈区

​ 在定义变量时,变量名与变量值都是需要存储的,分别对应内存中的两块区域:堆区与栈区。

  • ① 变量名与值内存地址的关联关系存放于栈区

  • ②变量值存放于堆区,内存管理回收的则是堆区的内容

标记过程
  1. 遍历所有的GC Roots对象(栈区中的所有内容或者线程都可以作为GC Roots对象)
  2. 将所有GC Roots的对象可以直接或间接访问到的对象标记为存活的对象,其余的均为非存活对象,应该被清除。

4.1.3 分代回收

分代

  • 在历经多次扫描的情况下,都没有被回收的变量,gc机制就会认为,该变量是常用变量,gc对其扫描的频率会降低

分代指的是根据存活时间来为变量划分不同等级(也就是不同的代)

  1. 新定义的变量,放到新生代这个等级中

    假设每隔1分钟扫描新生代一次,如果发现变量依然被引用,那么该对象的权重(权重本质就是个整数)加一

  2. 当变量的权重大于某个设定得值(假设为3),会将它移动到更高一级的青春代

    青春代的gc扫描的频率低于新生代(扫描时间间隔更长),假设5分钟扫描青春代一次,这样每次gc需要扫描的变量的总个数就变少了,节省了扫描的总时间

  3. 接下来,青春代中的对象,也会以同样的方式被移动到老年代中

    也就是等级(代)越高,被垃圾回收机制扫描的频率越低

回收

回收依然是使用引用计数作为回收的依据

分代回收的缺点

虽然分代回收可以起到提升效率的效果,但也存在一定的缺点:

例如一个变量刚刚从新生代移入青春代,该变量的绑定关系就解除了,该变量应该被回收,但青春代的扫描频率低于新生代,这就到导致了应该被回收的垃圾没有得到及时地清理。

没有十全十美的方案:

毫无疑问,如果没有分代回收,即引用计数机制一直不停地对所有变量进行全体扫描,可以更及时地清理掉垃圾占用的内存,但这种一直不停地对所有变量进行全体扫描的方式效率极低,所以我们只能将二者中和。

综上

垃圾回收机制是在清理垃圾&释放内存的大背景下,允许分代回收以极小部分垃圾不会被及时释放为代价,以此换取引用计数整体扫描频率的降低,从而提升其性能,这是一种以空间换时间的解决方案目录

猜你喜欢

转载自www.cnblogs.com/j-chao/p/12896707.html