垃圾回收是 Python 自带的机制,用于自动释放不会再用到的内存空间;

什么是内存泄漏呢?

  • 内存泄漏,并不是说你的内存出现了信息安全问题,被恶意程序利用了,而是指程序本身没有设计好,导致程序未能释放已不再使用的内存。
  • 内存泄漏也不是指你的内存在物理上消失了,而是意味着代码在分配了某段内存后,因为设计错误,失去了对这段内存的控制,从而造成了内存的浪费。

计数引用

  Python 中一切皆对象。当这个对象的引用计数(指针数)为 0 的时候,说明这个对象永不可达,自然它也就成为了垃圾,需要被回收。

  例:

# 显示当前 python 程序占用的内存大小
def show_memory_info(hint):
pid = os.getpid()
p = psutil.Process(pid) info = p.memory_full_info()
memory = info.uss / 1024. / 1024
print('{} memory used: {} MB'.format(hint, memory))
def func():
show_memory_info('initial')
a = [i for i in range(10000000)]
show_memory_info('after a created') func()
show_memory_info('finished') ########## 输出 ##########
# initial memory used: 6.62890625 MB
# after a created memory used: 199.33203125 MB
# finished memory used: 7.6640625 MB

  程序初始化时占的内存为6MB,接着创建了一个列表a,由于a还没被回收,因此占的内存升到了200MB,当函数返回后,a的引用计数为0,a被回收,内存又恢复到了7MB。

  如果把a变成全局变量,函数返回后,引用计数依然大于0,于是对象就不会被垃圾回收,依然占着大量的内存

def func():
show_memory_info('initial')
global a
a = [i for i in range(10000000)]
show_memory_info('after a created') func()
show_memory_info('finished') ########## 输出 ########## # initial memory used: 6.67578125 MB
# after a created memory used: 199.30859375 MB
# finished memory used: 199.30859375 MB

  或者把列表返回,在主程序中接收,引用依然存在,垃圾回收就不会被触发,大量内存仍然被占用着

def func():
show_memory_info('initial')
a = [i for i in range(10000000)]
show_memory_info('after a created')
return a a = func()
show_memory_info('finished') ########## 输出 ########## # initial memory used: 6.6484375 MB
# after a created memory used: 199.2890625 MB
# finished memory used: 199.2890625 MB

  看一下 Python 内部的引用计数机制

import sys

a = []

# 两次引用,一次来自 a,一次来自 getrefcount
print(sys.getrefcount(a)) def func(a):
# 四次引用,a,python 的函数调用栈,函数参数,和 getrefcount
print(sys.getrefcount(a)) func(a) # 两次引用,一次来自 a,一次来自 getrefcount,函数 func 调用已经不存在
print(sys.getrefcount(a)) ########## 输出 ########## 2
4
2

  sys.getrefcount() 这个函数,可以查看一个变量的引用次数。这段代码本身应该很好理解,不过别忘了,getrefcount 本身也会引入一次计数。另一个要注意的是,在函数调用发生的时候,会产生额外的两次引用,一次来自函数栈,另一个是函数参数。

  又如:

import sys

a = []

print(sys.getrefcount(a)) # 两次

b = a

print(sys.getrefcount(a)) # 三次

c = b
d = b
e = c
f = e
g = d print(sys.getrefcount(a)) # 八次 ########## 输出 ########## 2
3
8

  a、b、c、d、e、f、g 这些变量全部指代的是同一个对象,而 sys.getrefcount() 函数并不是统计一个指针,而是要统计一个对象被引用的次数,所以最后一共会有八次引用。

  手动释放内存,应该怎么做呢? 方法同样很简单。只需要先调用 del a 来删除一个对象;然后强制调用 gc.collect(),即可手动启动垃圾回收。

import gc
import os
import psutil
# 显示当前 python 程序占用的内存大小
def show_memory_info(hint):
pid = os.getpid()
p = psutil.Process(pid) info = p.memory_full_info()
memory = info.uss / 1024. / 1024
print('{} memory used: {} MB'.format(hint, memory)) show_memory_info('initial') a = [i for i in range(10000000)] show_memory_info('after a created') del a
gc.collect() show_memory_info('finish')
print(a) initial memory used: 6.54296875 MB
after a created memory used: 199.17578125 MB
finish memory used: 7.26171875 MB
Traceback (most recent call last):
File "Coroutine.py", line 24, in <module>
print(a)
NameError: name 'a' is not defined

循环引用

  观察代码:

def func():
show_memory_info('initial')
a = [i for i in range(10000000)]
b = [i for i in range(10000000)]
show_memory_info('after a, b created')
a.append(b)
b.append(a) func()
show_memory_info('finished') ########## 输出 ##########

initial memory used: 6.625 MB
  after a, b created memory used: 392.08984375 MB
  finished memory used: 392.08984375 MB

  这里,a 和 b 互相引用,并且,作为局部变量,在函数 func 调用结束后,a 和 b 这两个指针从程序意义上已经不存在了。但是,很明显,依然有内存占用!为什么呢?因为互相引用,导致它们的引用数都不为 0。

  处理这种情况,可以调用显式调用 gc.collect() ,来启动垃圾回收。

  Python 使用标记清除(mark-sweep)算法和分代收集(generational),来启用针对循环引用的自动垃圾回收。

调试内存泄漏

  objgraph,一个非常好用的可视化引用关系的包.

  安装:

pip install graphviz
pip install xdot
pip install objgraph

  windows的话要除了装以上库还要在官网https://graphviz.gitlab.io/_pages/Download/Download_windows.html下载,然后设置环境变量 Path增加C:\Program Files (x86)\Graphviz2.38\bin,在CMD输入dot -version验证。

  通过下面这段代码和生成的引用调用图,你能非常直观地发现,有两个 list 互相引用,说明这里极有可能引起内存泄露。

import objgraph

a = [1, 2, 3]
b = [4, 5, 6] a.append(b)
b.append(a) objgraph.show_refs([a])

     

  注:在windows中可能会提示:

Graph written to C:\Users\Public\Documents\Wondershare\CreatorTemp\objgraph-wwcqiie_.dot (8 nodes)
Image renderer (dot) not found, not doing anything else

  这时只要在打开dot文件所在的路径,然后CMD中执行

 dot .\objgraph-yclwfpzr.dot -Tpng -o image.png

  就可以生成文件。

  另一个非常有用的函数,是 show_backrefs()。以下是调用show_backrefs()生成的图片。

  

参考

  极客时间《Python核心技术与实战》专栏

Python进阶:程序界的垃圾分类回收的更多相关文章

  1. 【python进阶】Garbage collection垃圾回收2

    前言 在上一篇文章[python进阶]Garbage collection垃圾回收1,我们讲述了Garbage collection(GC垃圾回收),画说Ruby与Python垃圾回收,Python中 ...

  2. 【python进阶】Garbage collection垃圾回收1

    前言 GC垃圾回收在python中是很重要的一部分,同样我将分两次去讲解Garbage collection垃圾回收,此篇为Garbage collection垃圾回收第一篇,下面开始今天的说明~~~ ...

  3. python进阶(7)垃圾回收机制

    Python垃圾回收 基于C语言源码底层,让你真正了解垃圾回收机制的实现 引用计数器 标记清除 分代回收 缓存机制 Python的C源码(3.8.2版本) 1.引用计数器 1.1环状双向链表 refc ...

  4. Python进阶 - 对象,名字以及绑定

    Python进阶 - 对象,名字以及绑定 1.一切皆对象 Python哲学: Python中一切皆对象 1.1 数据模型-对象,值以及类型 对象是Python对数据的抽象.Python程序中所有的数据 ...

  5. python进阶篇

    python进阶篇 import 导入模块 sys.path:获取指定模块搜索路径的字符串集合,可以将写好的模块放在得到的某个路径下,就可以在程序中import时正确找到. ​ import sys ...

  6. Python进阶(三十五)-Fiddler命令行和HTTP断点调试

    Python进阶(三十五)-Fiddler命令行和HTTP断点调试 一. Fiddler内置命令   上一节(使用Fiddler进行抓包分析)中,介绍到,在web session(与我们通常所说的se ...

  7. python进阶02 特殊方法与特殊属性

    python进阶02 特殊方法与特殊属性 一.初始化.析构 1.初始化 # python中有很多双下划线开头且以下划线结尾的固定方法,它们会在特定的时机被触发执行,这便是特殊方法 # 在实例化的时候就 ...

  8. Python进阶----线程基础,开启线程的方式(类和函数),线程VS进程,线程的方法,守护线程,详解互斥锁,递归锁,信号量

    Python进阶----线程基础,开启线程的方式(类和函数),线程VS进程,线程的方法,守护线程,详解互斥锁,递归锁,信号量 一丶线程的理论知识 什么是线程:    1.线程是一堆指令,是操作系统调度 ...

  9. Python进阶----进程间数据隔离, join阻塞等待, 进程属性, 僵尸进程和孤儿进程, 守护进程

    Python进阶----进程间数据隔离, join阻塞等待, 进程属性, 僵尸进程和孤儿进程, 守护进程 一丶获取进程以及父进程的pid 含义:    进程在内存中开启多个,操作系统如何区分这些进程, ...

随机推荐

  1. SpringBoot导入Excel数据到MySQL数据库

    package com.example.example1.Controller; import com.example.example1.Entity.User; import com.example ...

  2. 微信小程序 wxs的简单应用

    Demo地址:微信小程序wxs的简单应用 案例分析 张三.李四.王五,各自分别都有数量不等的车,现在需要列表显示名字及他们拥有车的数量, list数据结构如下,当我们使用wx:for进行显示时,发现个 ...

  3. 微信小程序 报错:Setting data field "xxx" to undefined is invalid

    通过网络请求获取的数据,当返回的数据没有xxx(变量名)这个变量时,此时xxx是undefined 若使用setData进行赋值,则会报如下的错误: Setting data field " ...

  4. CentOS 7 安装 MySql8.0

    1-配置 Yum 库 rpm -Uvh https://repo.mysql.com/mysql80-community-release-el7-3.noarch.rpm 2-安装 MySql 2.1 ...

  5. java单元测试之如何实现异步接口的测试案例

    测试是软件发布的重要环节,单元测试在实际开发中是一种常用的测试方法,java单元测试主要用junit,最新是junit5,本人开发一般用junit4.因为单元测试能够在软件模块组合之前尽快发现问题,所 ...

  6. c 越界

    int main(int argc, char* argv[]){ int i = 0; int arr[3] = {0}; for(; i<=3; i++){ arr[i] = 0; prin ...

  7. Mxnet学习笔记(3)--自定义Op

    https://blog.csdn.net/u011765306/article/details/54562282 前言 今天因为要用到tile操作(类似np.tile,将数据沿axises进行数据扩 ...

  8. [转]使用 curl 发送 POST 请求的几种方式

    HTTP 的 POST 请求通常是用于提交数据,可以通过这篇文章来了解各种提交方式:四种常见的 POST 提交数据方式.做 Web 后端开发时,不可避免地要自己给自己发请求来调试接口,这里要记录的内容 ...

  9. git让线上代码强制覆盖本地的

    git强制覆盖本地命令(分步执行): git fetch --all    git reset --hard origin/master    git pull git强制覆盖本地命令(单条执行):  ...

  10. c++11 为什么使用ref,和引用的区别

    std::ref只是尝试模拟引用传递,并不能真正变成引用,在非模板情况下,std::ref根本没法实现引用传递,只有模板自动推导类型时,ref能用包装类型reference_wrapper来代替原本会 ...