前言
内存泄漏在编程中是一个严重的问题,可能导致程序性能下降、系统不稳定甚至崩溃。
目录
危害
- 性能下降:可用内存减少,导致系统频繁进行内存交换,使程序运行速度变慢。
- 不稳定:内存耗尽可能导致程序出现异常、崩溃或产生不可预测的结果。
- 资源浪费:系统资源被无效占用,影响其他程序的正常运行。
风险
- 资源未释放:如果打开的文件、网络连接、数据库连接等资源在使用后没有正确关闭和释放,随着程序的运行,这些未释放的资源会不断累积,占用越来越多的内存。
- 无限增长的数据结构:例如,不断向一个列表或字典添加元素,而没有在适当的时候删除不再需要的部分,可能导致数据结构无限增长,消耗大量内存。
- 循环引用:对象之间的循环引用可能导致它们的引用计数永远不为零,即使它们不再被程序的其他部分直接使用,也无法被垃圾回收器回收。
- 缓存未清理:一些程序为了提高性能会使用缓存,但如果缓存中的数据不再被使用时没有及时清理,也会导致内存占用不断增加。
动态内存分配
在 Python 中,动态内存分配是自动处理的。
当您创建对象(例如列表、字典、类的实例等)时,Python 会自动为这些对象分配所需的内存并为该对象维护一个引用计数。当这些对象不再被引用时(引用计数变为 0 ),Python 的垃圾回收机制会自动回收它们所占用的内存。
引用计数
这是 Python 内存管理的基础机制。每个对象都有一个引用计数,记录着有多少个地方正在引用它。当引用计数变为 0 时,该对象就会被立即释放。
测试范例
引用数统计方法:采用sys模块的getrefcount函数去获取变量的引用数.
python
import sys
d = [1, 2, 3]
# 一开始引用次数为1
# 若调用getrefcount函数 引用次数+1 为2
# print(sys.getrefcount(d))
d_dict = {}
person_list = ["张三", "李四", '老王', "杜甫"]
for p in person_list:
d_dict[p] = d
print(sys.getrefcount(d))
# 删除变量d_dict后
del d_dict
# 手动触发垃圾回收
print(sys.getrefcount(d))
内存池
这种存储方式通过复用已有的字符串对象,减少了内存分配和释放的操作,提高了性能和内存使用效率。
在 Python 中,以下类型的对象可能会进入内存池:
- 小整数对象(范围通常是 [-5, 256] 之间的整数)。
- 短字符串(长度较短且经常使用的字符串)。
验证方法
两个字符串变量指向的都是同一个id
python
a = "hello"
b = "hello"
print(id(a) == id(b))
垃圾回收
Python 中主要使用以下两种垃圾回收机制
引用计数:这是 Python 最基本的垃圾回收机制。每个对象都有一个引用计数,当引用计数变为 0 时,对象就会被立即回收。
循环引用垃圾回收器:用于处理循环引用导致的内存无法释放的情况。它使用了标记 - 清除算法或者分代回收算法来检测和回收不可达的对象。
标记 - 清除
标记阶段:从根对象(例如正在运行的函数的局部变量和全局变量)开始,沿着对象之间的引用关系进行遍历,将所有可达的对象进行标记。
清除阶段:对未被标记的对象进行回收,释放它们所占用的内存空间。
分代回收的策略
将对象分为不同的代(通常是 0 代、1 代、2 代),
并根据不同代的特点和对象的生存时间来决定何时进行垃圾回收,以提高垃圾回收的效率。
设计原则
熟悉 Python 中对象的引用计数机制,那我们的程序设计最好遵守下面的设计原则,以此避免内存泄露的事件.