python外篇(内存泄露)

  • Post author:
  • Post category:python



目录


了解


循环引用造成的内存泄露


大量创建对象造成的内存泄漏


全局对象造成的内存泄露


不适当缓存造成的内存泄露


内存分析工具


了解



### 以下为Python中可能会出现内存泄露的情况:



(1) 循环引用:当两个或多个对象相互引用,造成的循环引用进而导致内存泄露

(2) 大量创建对象:当程序中频繁创建大量的对象并没有及时销毁,也会导致内存泄露。

(3) 全局变量:当全局变量被创建后一直存在,即使它们不再被使用,也会占用内存空间,可能导致内存泄露。

(4) 不适当的缓存使用:如果在缓存中存储了大量数据,而没有适当地删除旧数据,会导致内存泄漏。

(5) C扩展模块:使用C扩展模块编写的代码可能会出现内存泄漏问题,因为C扩展代码不受Python的垃圾回收机制控制。



### 以下为避免这些情况导致的内存泄漏的措施:



(1) 尽可能避免循环引用,使用weakref库避免对象之间相互引用。

(2) 及时销毁不需要的对象,特别是大量创建的对象。

(3) 减少全局变量的使用,只在必要时使用。

(4) 注意缓存的使用,需要定期清理缓存,避免缓存数据过多导致内存泄漏。

(5) 尽可能避免使用C扩展模块,如果必须使用,需要仔细检查代码,确保它没有导致内存泄漏问题。

(6) 使用生成器和迭代器:生成器和迭代器是Python中用于处理大型数据集合的高效工具。它们可以逐个返回数据,而不是一次性返回整个数据集合。使用生成器和迭代器可以减少对内存的需求,避免内存泄漏。

(7) 使用with语句:with语句是Python中用于管理资源的一种语法,它可以自动关闭文件和数据库连接等资源,避免资源未被正确释放导致的内存泄漏。

(8) 使用内存分析工具:Python中有一些内存分析工具可以帮助我们检测和解决内存泄漏问题,例如pympler和memory_profiler等工具。使用这些工具可以定位内存泄漏的代码,并进行优化。

循环引用造成的内存泄露



### 介绍:



在python中,都知道当一个变量不被引用的时候(引用计数为0)就会触发垃圾回收机制从而被从内存中删除,但如果在两个对象间去互相引用(你引用我,我也引用你),这样就会形成一个无限循环引用(你引用我,我引用你,你又引用我,我又引用你…),那么此时两个对象一直处于被引用状态,意味着逃避了垃圾回收机制的回收,在内存中一直循环引用占用着内存资源,从而导致这些内存资源将不能被再次使用。这就是循环引用造成的内存泄露问题。



### 代码问题阐述:

class Person:
    def __init__(self, name):
        self.name = name
        self.cars = []

    def add_car(self, car):
        self.cars.append(car)
        car.owner = self


class Car:
    def __init__(self, kind):
        self.kind = kind
        self.owner = None


person = Person("Joden")
car = Car("Tesla")
person.add_car(car)
# car.owner = self
# self.cars = [car]



### 代码解决阐述:


(1) 以下为解决阐述:


使用弱引用,弱引用不会增加一个变量或对象的引用计数


(2) 以下为解决代码阐述:

import weakref


class Person:
    def __init__(self, name):
        self.name = name
        self.cars = []

    def add_car(self, car):
        self.cars.append(weakref.ref(car))
        car.owner = weakref.ref(self)


class Car:
    def __init__(self, kind):
        self.kind = kind
        self.owner = None


person = Person("Joden")
car = Car("Tesla")
person.add_car(car)
# car.owner = weakref.ref(self)
# self.cars = [weakref.ref(car)]

大量创建对象造成的内存泄漏



### 介绍:



当创建大量对象,并且不能及时被垃圾回收机制回收,从而导致的内存泄露问题



### 代码问题阐述:

class MyClass:
    def __init__(self, data):
        self.data = date


objs = []
for i in range(1000000):
    obj = MyClass(i)
    objs.append(obj)


阐述:在这个示例中,我们构建了一个耗时的循环创建对象,并且不断将对象添加到列表中,这样在循环的过程中列表就无法被短时间内释放,就会在此期间一直占用内存,从而造成内存泄露问题



### 代码解决阐述:

class MyClass:
    def __init__(self, data):
        self.data = date


def create_objs(num):
    for i in range(num):
        obj = MyClass(i)
        yield obj


阐述:在这个示例中,我们将创建对象过程放在了函数内并且用生成器来返回我们创建的对象,这样只有需要时才会去创建对象就避免了,大量创建对象造成的内存泄露问题

全局对象造成的内存泄露



### 介绍:



在了解这个全局变量造成的内存泄露之前还需要了解两个知识:深拷贝和浅拷贝、全局变量。

深拷贝和浅拷贝:在python中对基本数据类型的引用都是浅拷贝(实际上就是对内存id的引用,本质上为指向同一个内存地址的引用),而深拷贝就不一样了,比如对象、list、dict等等,它们被引用时就会开辟新的内存空间来存储这些数据。

全局变量:很简单,创建一个py文件(模块),直接在py文件内定义的变量就是全局变量(而非在函数内等局部作用域中定义的变量)。

那么,现在就很好理解全局变量造成的内存泄露问题了。当某些模块中的全局变量被程序中多次引用,那么每次引用就会重新开辟内存空间来存储,这样就会占用大量的内存空间从而造成内存泄露问题。

对于如何解决这个问题,方案是单例模式,单例模式可以确保类在内存中只有一个实例然后在整个程序中都会对其引用,这样就避免了全局变量被多次在内存中创建从而导致的内存泄露问题。、



### 代码问题阐述:

my_list = [...]


这个my_list在程序中被多次引用,而且倘若my_list占用较大空间



### 代码解决阐述:

class Singleton:
    __instance = None

    def __new__(cls, *args, **kwargs):
        if not cls.__instance:
            cls.__instance = super().__new__(cls)
            cls.__instance.my_list = []
        return cls.__instance


def add_data(new_data):
    obj = Singleton()
    obj.my_list.append(new_data)


def get_data():
    obj = Singleton()
    return obj.my_list


add_data(1)
add_data(2)
add_data(3)
get_list = get_data()

不适当缓存造成的内存泄露

### 了解:

对于这个“不适当的缓存造成的内存泄露”是我们使用缓存原理要注意的,如果我们的缓存中数据量过大时就有一定的风险了,如果这个缓存在短时间内没被清除且一直变大就会占用越来越多的内存就有可能造成内存泄露,而且如果程序运行过程中突然崩溃强制关闭,此时内存中的数据也可能无法清理总而造成内存泄露。

那么,对于解决“不适当的缓存造成的内存泄露”问题呢?其实本质上也是由长时间引用没被释放造成的,所以可以使用弱引用来解决。

### 问题代码阐述:

class Calculator:
    __cache = {}

    @classmethod
    def add(cls, a, b):
        key = f"add_{a}_{b}"
        if key in cls.__cache:
            return cls.__cache[key]
        result = a + b
        cls.__cache[key] = result
        return result

在这个示例中我们使用字典作为加法计算的缓存,如果已经计算过了就直接引用,否则将添加到缓存字典中,考虑假若它是我们python底层实现的“加法”,我们在一个大型项目程序中将会多少次调用它,所以如果不能及时释放缓存,缓存反而会弄巧成拙

### 解决代码阐述:

import weakref


class Calculator:
    __cache = weakref.WeakValueDictionary()

    @classmethod
    def add(cls, a, b):
        key = f"add_{a}_{b}"
        result = cls.__cache.get(key)
        if result is None:
            result = a + b
            cls.__cache[key] = result
        return result

内存分析工具



### 工具介绍(需要自己去了解)



gc:python内置的内存分析模块

objgraph:用于生成 Python 对象引用图,可以帮助我们找出引用计数不正确的对象。

memory_profiler:用于逐行分析 Python 代码的内存使用情况,可以帮助我们找出内存占用过高的代码段。

pympler:包含了多个子模块,可以帮助我们分析 Python 中的内存使用情况,包括对象分配、垃圾回收、内存泄露等。

heapy:基于 Guppy,可以用于分析 Python 中的内存使用情况,包括内存分配情况、对象大小分布、内存泄露等。

我们可以在程序中使用这些工具,来查看内存使用情况、对象数量等信息,以及跟踪内存泄露和内存占用过高的问题。具体使用方法可以参考对应工具的官方文档。



版权声明:本文为m0_60633107原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。