介绍
在Python 程序运行的时候,会在内存中开辟一块空间,用于存放临时变量;当计算完成之后,就会将结果输出到永久存储器中,如果数据量特别大,那内存空间管理部妥当的话就非常容易爆内存,程序可能直接终止。在Python中,一切皆对象。所以,每一个变量,实际上都是对象的一个指针。所以,当这个对象的引用计数(指针数)为0的时候,说明它也变成了垃圾,需要被放到回收箱中。OS模块
与操作系统交互的库psutill模块
与系统交互的库,能够轻松实现获取系统运行的进程和系统利用率(包括CPU、内存、磁盘、网络等)信息,它是用来做系统监控、性能分析、进程管理。 import os import psutil def show_info(start): # 获取当前进程id pid=os.getpid() # 获取当前堆成对象 p=psutil.Process(pid) # 获取进程独自占用的物理内存 换算单位MB info=p.memory_full_info() memory=info.uss/1024./1024 print(f'{start}一共占用{memory:2f}MB') def func(): show_info('initial') a=[i for i in range(1000000)] show_info('created') func() show_info('finished') import os import psutil def show_info(start): # 获取当前进程id pid=os.getpid() # 获取当前堆成对象 p=psutil.Process(pid) # 获取进程独自占用的物理内存 换算单位MB info=p.memory_full_info() memory=info.uss/1024./1024 print(f'{start}一共占用{memory:2f}MB') def func(): show_info('initial') global a # a是局部变量,用global 声明它是全局变量 a=[i for i in range(1000000)] show_info('created') func() show_info('finished') import psutil def show_info(start): # 获取当前进程id pid=os.getpid() # 获取当前堆成对象 p=psutil.Process(pid) # 获取进程独自占用的物理内存 换算单位MB info=p.memory_full_info() memory=info.uss/1024./1024 print(f'{start}一共占用{memory:2f}MB') def func(): show_info('initial') a=[i for i in range(1000000)] show_info('created') return a a=func() show_info('finished') 当a是局部变量时,在返回到函数调用处时,局部变量的引用会注销。这时,列表a所指代对象的引用数为0,Python便会执行垃圾回收,因此之前占用的内存被收回了。当a是全局变量时,即使函数体内代执行完毕,返回到函数调用处时,对列表a的引用仍然是存在的,所以对象不会被垃圾回收,仍然占用大量内存。Python 内部的引用计数机制
可以通过sys.getrefcount() 这个函数,来了解**Python内部的引用计数机制。 import sys a=[1,2,3] # print(sys.getrefcount(a)) # a这里引用了 2 次 def func(a): # 这里调用a 4次 # a本身一次 函数调用1次 函数参数一次 getrefcount 一次 print(sys.getrefcount(a)) func(a) import sys a=[1,2,3] def func(a): print(sys.getrefcount(a)) func(a) # a作为形参相当于函数体内的临时变量 所以 调用执行完毕会被释放掉 引用次数为0 # 2 a 本身一次 getrefcount 一次 print(sys.getrefcount(a)) import sys a=[1,2,3] print(sys.getrefcount(a)) # 2 b=a print(sys.getrefcount(a)) # 3 c=b d=c print(sys.getrefcount(a)) # 5 # 1.getrefcount() 只计一次运用次数 # 2.变量赋值 b变量指向了 a所在内存地址 getrefcount 本身也会引入一次计数手动启动垃圾回收
如果我们可以在手动删除完对象的引用,然后强调用gc.collect()清除没有引用的对象,其实也就是手动的启动对象的回收。 import sys import gc a = [1,2,3] print(sys.getrefcount(a)) # a=None # 相当于将a 变量指向了 None del a # 相当于自己把对象的引用删掉 本质上对象还没有被删除 gc.collect() # 手动启动回收 print(a) # NameError: name 'a' is not defined 此时a 已经被回收 import sys import gc a = 1 # 小整数对象池 b=10000 print(sys.getrefcount(a)) # 134 print(sys.getrefcount(b)) # 4循环引用
如果有两个对象,他们互相引用,并且不再被别的对象引用,那么它们应该被垃圾回收吗? import os import psutil import gc ''' 引用次数 为0 的时候 一定会启用 垃圾回收吗 垃圾回收 一定是引用次数 为0的时候吗 充分必要条件 ''' def show_info(start): # 获取当前进度id pid=os.getpid() # 获取当前堆成对象 p=psutil.Process(pid) # 返回该对象的内存消耗 info = p.memory_full_info() # 获取进程独自占用的物理内存 换算单位 MB memory=info.uss/1024/1024 print(f'{start}一共占用{memory:2f}MB') def func(): show_info('initial') a=[i for i in range(1000000)] b=[i for i in range(1000000)] show_info('created') # 相互引用 a.append(b) b.append(a) a=func() gc.collect() # 手动回收 如果引用次数不为0时 手动回收 也是可以的 show_info('finished') 总而言之,当双向引用的时候,引用计数虽然还在,但我们可以手动拉起来回收,进行释放内存,所以,引用次数是垃圾回收的充分非必要条件调试内存泄漏
在Python 中通过引用计数和垃圾回收来管理内存,但是在一定情况下也会产生内存泄露 第一是对象被另一个生命周期特别长得对象所引用第二是循环引用中的对象定义了 __del __函数objgraph,一个非常好用的可视化引用关系的包。在这个包中的 show_refs(),它可以清晰的引用关系图。
import objgraph a=[1,2,3] b=[4,5,6] a.append(b) b.append(a) objgraph.show_refs(a) .dot 文件转图片:https://onlineconvertfree.com/如何使用pdb
首先,要启动pdb调试,我们只需要在程序中,加入 import pdb和pdb.set_trace() 这两行代码就行。 a=1 b=2 import pdb pdb.set_trace c=3 print(a+b+c) 这时,我们就可以执行,在IDE 断点调用器中可以执行的一切操作,比如打印,语法是’p’: (pdb) p a 1 (pdb) p b 2 除了打印,常见的操作还有 ‘n’ ,表示继续执行代码到下一行 (pdb)n -->print(a+b+c) 而命令!,则表示列举出当前代码行上下的11行源代码,方便开发者熟悉当前断点周围的代码状态 (pdb)1 a=1 b=2 import pdb pdb.set_trace() -> c=3 print(a+b+c) 命令 ‘s’ ,就是step into 的意思,即进入相应对应的代码内部当然,除了这些常用命令,还有许多其它的命令可以调用参考对应的官方文档:https//docs.python.org/3/library/pdb.htm|#module-pdb)参数介绍
ncalls: 函数调用的次数,如果这一有两个值,就表示有递归调用,第二个值是原生调用次数,第一个值是总调用次数。tottime: 函数内部消耗的总时间。(可以帮助优化)percall: 是tottime 除以ncalls,一个函数每次调用平均消耗时间cumtime:之前所有子函数消费时间的累积和。filename: lineno(function): 被分析函数所在文件名,行号,函数名。注意
列表为可变类型 li+=1 相当于改变li本身li =li+1 相当于li 是两个变量 id 不一致 返回的是 原本的li 元组为不可变类型tu +=1 也就是重新创建了一个 tu 变量 id 不一致。