猿问

在Python中丢弃图片时发生内存泄漏

我目前正在用Python编写一个简单的棋盘游戏,我刚刚意识到垃圾回收并不会在重新加载图像时从内存中清除丢弃的位图数据。它仅在启动或加载游戏或分辨率改变时才会发生,但是它会使消耗的内存倍增,因此我无法解决此问题。


重新加载图像时,所有引用都将传输到新图像数据,因为它绑定到与原始图像数据绑定到的变量相同的变量。我试图通过使用强制垃圾收集,collect()但没有帮助。


我写了一个小样本来演示我的问题。


from tkinter import Button, DISABLED, Frame, Label, NORMAL, Tk

from PIL.Image import open

from PIL.ImageTk import PhotoImage


class App(Tk):

    def __init__(self):

        Tk.__init__(self)

        self.text = Label(self, text = "Please check the memory usage. Then push button #1.")

        self.text.pack()

        self.btn = Button(text = "#1", command = lambda : self.buttonPushed(1))

        self.btn.pack()


    def buttonPushed(self, n):

        "Cycle to open the Tab module n times."

        self.btn.configure(state = DISABLED) # disable to prevent paralell cycles

        if n == 100:

            self.text.configure(text = "Overwriting the bitmap with itself 100 times...\n\nCheck the memory usage!\n\nUI may seem to hang but it will finish soon.")

            self.update_idletasks()

        for i in range(n):      # creates the Tab frame whith the img, destroys it, then recreates them to overwrite the previous Frame and prevous img

            b = Tab(self)

            b.destroy()

            if n == 100:

                print(i+1,"percent of processing finished.")

        if n == 1:

            self.text.configure(text = "Please check the memory usage now.\nMost of the difference is caused by the bitmap opened.\nNow push button #100.")

            self.btn.configure(text = "#100", command = lambda : self.buttonPushed(100))

        self.btn.configure(state = NORMAL)  # starting cycles is enabled again       


要运行上面的代码,您将需要一个PNG图像文件。我的map.png的尺寸是1062×1062。作为PNG,它是1.51 MB,而作为位图数据,它是3-3.5 MB。使用大图像可以轻松查看内存泄漏。


运行我的代码时的预期结果:python的进程逐周期消耗内存。当它消耗约500 MB的内存时,它会崩溃但又开始耗尽内存。


请给我一些建议,以解决这个问题。感谢您的帮助。谢谢你。提前。


幕布斯7119047
浏览 269回答 2
2回答

海绵宝宝撒

首先,您绝对没有内存泄漏。如果它在接近500MB时就“崩溃”并且永不越过,则可能不会泄漏。我的猜测是,您根本不会有任何问题。当Python的垃圾回收器清理掉所有东西时(通常在CPython中使用完后立即发生),它实际上并没有真正释放内存到OS。相反,它可以解决问题,以备日后需要时使用。这是有意的—除非您不愿意进行交换,但是重用内存比保持释放和重新分配要快得多。另外,如果500MB是虚拟内存,那么在现代64位平台上就没有任何内容了。如果它没有映射到物理/驻留内存(或者在计算机空闲时被映射,否则很快就被扔掉了),这不是问题。只是操作系统对有效免费的资源非常满意。更重要的是:是什么让您认为有问题?是否有任何实际症状,或者在程序管理器/活动监视器/顶部/什么让您感到害怕?(如果是后者,请查看其他程序。在我的Mac上,我目前有28个程序正在使用超过400MB的虚拟内存运行,并且我正在使用16GB中的11个程序,即使少于3GB例如,如果我启动Logic,则将以比Logic可用的速度更快的速度收集内存;在那之前,操作系统为何要浪费精力去映射内存(尤其是当它无法确保某些进程无法进行映射时)去要求它以后不使用的内存)?但如果是一个真正的问题,有两种方法可以解决这个问题。第一个技巧是在子进程中执行所有内存密集型操作,您可以杀死该进程并重新启动以恢复临时内存(例如,使用multiprocessing.Process或concurrent.futures.ProcessPoolExecutor)。这通常会使事情变慢而不是变快。而且,当临时存储器大部分是直接进入GUI的东西,因此必须驻留在主进程中时,显然这并不容易。另一个选择是弄清楚内存的使用位置,而不是同时保留这么多对象。基本上,有两个部分:首先,在每个事件处理程序结束之前释放所有可能的东西。这意味着调用close文件(将del对象或将对它们的所有引用都设置为)None,调用destroy不可见的GUI对象,并且最重要的是,不存储对不需要的对象的引用。(PhotoImage使用后,您实际上是否需要保持周围环境?如果这样做,有什么方法可以按需加载图像?)接下来,请确保您没有参考周期。在CPython中,只要没有循环,就立即清除垃圾-但是,如果有的话,它们会一直存在直到循环检查器运行为止。您可以使用该gc模块进行调查。要做的一件非常快的事情就是经常尝试:print(gc.get_count())gc.collect()print(gc.get_count())如果看到巨大的水滴,则说明有周期。您将必须查看gc.getobjects()和的内部gc.garbage,或附加回调,或者只是对您的代码进行推理才能找到循环的确切位置。对于每个引用,如果您真的不需要双向引用,请摆脱一个引用。如果您这样做,请将其中之一更改为weakref。
随时随地看视频慕课网APP

相关分类

Python
我要回答