求助:使用FigureCanvasTkAgg时Matplotlib内存泄漏无法回收
如何清理Tkinter应用中Matplotlib的内存占用?
我将Matplotlib官方的Tk嵌入示例代码放入循环后,程序内存占用持续飙升至1000MB。尝试了多种内存清理操作(包括清空图表、销毁组件等)均无法消除泄漏,仅通过复用图表而非每次新建能部分规避问题,但无法彻底解决。
通过tracemalloc检测发现,Python侧仅占用约1MB内存,泄漏源于C侧渲染器资源未被释放——即便已无对应引用。我使用的是Matplotlib 3.6.2版本(该版本本应修复同类泄漏问题),且在Windows系统的多个Python版本中均可复现此问题。
复现代码
import tkinter import matplotlib print(matplotlib._version.version) matplotlib.use("TkAgg") from matplotlib.backends.backend_tkagg import ( FigureCanvasTkAgg, NavigationToolbar2Tk) import gc from matplotlib.backend_bases import key_press_handler from matplotlib.figure import Figure import psutil import os, psutil process = psutil.Process(os.getpid()) import numpy as np import time root = tkinter.Tk() frame = tkinter.Frame(root) root.wm_title("Embedding in Tk") import matplotlib.pyplot as plt def my_func(): global root,frame fig = Figure(figsize=(5, 4), dpi=100) t = np.arange(0, 3, .01) ax = fig.add_subplot() line = ax.plot(t, 2 * np.sin(2 * np.pi * t)) ax.set_xlabel("time [s]") ax.set_ylabel("f(t)") canvas = FigureCanvasTkAgg(fig, master=frame) # A tk.DrawingArea. canvas.draw() # pack_toolbar=False will make it easier to use a layout manager later on. toolbar = NavigationToolbar2Tk(canvas, frame, pack_toolbar=False) toolbar.update() toolbar.pack(side=tkinter.BOTTOM, fill=tkinter.X) canvas.get_tk_widget().pack(side=tkinter.TOP, fill=tkinter.BOTH, expand=True) time.sleep(0.1) # 我尝试过的内存清理操作 ax = fig.axes[0] ax.clear() canvas.get_tk_widget().pack_forget() toolbar.pack_forget() canvas.figure.clear() canvas.figure.clf() canvas.get_tk_widget().destroy() toolbar.destroy() mem = process.memory_info().rss/2**20 print(mem) # 单位MB if mem > 1000: root.destroy() frame.destroy() frame = tkinter.Frame(root) root.after(10,my_func) gc.collect() if __name__ == "__main__": root.after(1000,my_func) root.mainloop()
可行的解决方法
针对C侧渲染器未释放的问题,需要补充更彻底的资源解绑与引用清除步骤:
1. 优化清理流程,切断核心关联
在销毁组件前,显式切断Canvas与Figure的关联,手动删除所有相关引用,确保C侧资源能被正确回收:
# 优化后的内存清理代码段 # 1. 切断Canvas与Figure的绑定,释放C侧渲染器关联 canvas.figure = None # 2. 销毁Tk组件 canvas.get_tk_widget().destroy() toolbar.destroy() # 3. 删除Python侧所有相关引用,避免引用计数阻碍回收 del ax, line, fig, canvas, toolbar # 4. 处理Frame并强制触发垃圾回收 frame.destroy() frame = tkinter.Frame(root) gc.collect() # 等待Tk事件循环完成销毁操作 root.update_idletasks()
2. 更换Backend尝试
若TkAgg后端仍存在泄漏,可尝试切换至其他后端,比如:
# 非交互场景可选Agg后端 matplotlib.use("Agg") # 需交互则改用Qt后端 # matplotlib.use("QtAgg")
3. 复用核心对象(最彻底的规避方式)
避免每次循环新建Figure、Canvas等对象,初始化时创建一次,后续仅更新数据:
# 初始化阶段仅创建一次核心对象 fig = Figure(figsize=(5, 4), dpi=100) ax = fig.add_subplot() line, = ax.plot([], []) ax.set_xlabel("time [s]") ax.set_ylabel("f(t)") canvas = FigureCanvasTkAgg(fig, master=frame) toolbar = NavigationToolbar2Tk(canvas, frame, pack_toolbar=False) # 完成布局 toolbar.pack(side=tkinter.BOTTOM, fill=tkinter.X) canvas.get_tk_widget().pack(side=tkinter.TOP, fill=tkinter.BOTH, expand=True) def my_func(): global line, ax # 仅更新数据与图表状态 t = np.arange(0, 3, .01) line.set_data(t, 2 * np.sin(2 * np.pi * t)) ax.relim() ax.autoscale_view() canvas.draw() time.sleep(0.1) mem = process.memory_info().rss/2**20 print(mem) if mem > 1000: root.destroy() root.after(10, my_func)
内容的提问来源于stack exchange,提问作者Ahmed AEK
相关产品推荐
相关产品推荐

