企业做网站的费用如果做账,加盟网站有哪些,网上做网站资金大概多少,建设网站要多少页面Python 程序在运行时#xff0c;需要在内存中开辟出一块空间#xff0c;用于存放运行时产生的临时变量#xff0c;计算完成后#xff0c;再将结果输出到永久性存储器中。但是当数据量过大#xff0c;或者内存空间管理不善#xff0c;就很容易出现内存溢出的情况#xff…Python 程序在运行时需要在内存中开辟出一块空间用于存放运行时产生的临时变量计算完成后再将结果输出到永久性存储器中。但是当数据量过大或者内存空间管理不善就很容易出现内存溢出的情况程序可能会被操作系统终止。而对于服务器这种用于永不中断的系统来说内存管理就显得更为重要了不然很容易引发内存泄漏。这里的内存泄漏是指程序本身没有设计好导致程序未能释放已不再使用的内存或者直接失去了对某段内存的控制造成了内存的浪费。Python 是通过什么机制来管理不会再用到的内存空间的呢Python引用计数机制在学习 Python 的整个过程中我们一直在强调Python 中一切皆对象也就是说在 Python 中你用到的一切变量本质上都是类对象。那么如何知道一个对象永远都不能再使用了呢很简单就是当这个对象的引用计数值为 0 时说明这个对象永不再用自然它就变成了垃圾需要被回收。举个例子import osimport 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))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: 47.19140625 MBafter a created memory used: 433.91015625 MBfinished memory used: 48.109375 MB注意运行此程序之前需安装 psutil 模块(获取系统信息的模块)可使用 pip 命令直接安装执行命令为 $pip install psutil如果遇到 Permission denied 安装失败请加上 sudo 重试。可以看到当调用函数 func() 且列表 a 被创建之后内存占用迅速增加到了 433 MB而在函数调用结束后内存则返回正常。这是因为函数内部声明的列表 a 是局部变量在函数返回后局部变量的引用会注销掉此时列表 a 所指代对象的引用计数为 0Python 便会执行垃圾回收因此之前占用的大量内存就又回来了。明白了这个原理后稍微修改上面的代码如下所示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: 48.88671875 MBafter a created memory used: 433.94921875 MBfinished memory used: 433.94921875 MB上面这段代码中global a 表示将 a 声明为全局变量则即使函数返回后列表的引用依然存在于是 a 对象就不会被当做垃圾回收掉依然占用大量内存。同样如果把生成的列表返回然后在主程序中接收那么引用依然存在垃圾回收也不会被触发大量内存仍然被占用着def func(): show_memory_info(initial) a [i for i in derange(10000000)] show_memory_info(after a created) return aa func()show_memory_info(finished)输出结果为initial memory used: 47.96484375 MBafter a created memory used: 434.515625 MBfinished memory used: 434.515625 MB以上最常见的几种情况下面由表及里深入看一下 Python 内部的引用计数机制。先来分析一段代码import simport ysa []# 两次引用一次来自 a一次来自 getrefcountprint(sys.getrefcount(a))def func(a): # 四次引用apython 的函数调用栈函数参数和 getrefcount print(sys.getrefcount(a))func(a)# 两次引用一次来自 a一次来自 getrefcount函数 func 调用已经不存在print(sys.getrefcount(a))输出结果为242注意sys.getrefcount() 函数用于查看一个变量的引用次数不过别忘了getrefcount 本身也会引入一次计数。另一个要注意的是在函数调用发生的时候会产生额外的两次引用一次来自函数栈另一个是函数参数。import sysa []print(sys.getrefcount(a)) # 两次b aprint(sys.getrefcount(a)) # 三次c bd be cf eg dprint(sys.getrefcount(a)) # 八次输出结果为238分析一下这段代码a、b、c、d、e、f、g 这些变量全部指代的是同一个对象而 sys.getrefcount() 函数并不是统计一个指针而是要统计一个对象被引用的次数所以最后一共会有 8 次引用。理解引用这个概念后引用释放是一种非常自然和清晰的思想。相比 C 语言中需要使用 free 去手动释放内存Python 的垃圾回收在这里可以说是省心省力了。如果想手动释放内存应该怎么做呢方法同样很简单只需要先调用 del a 来删除一个对象然后强制调用 gc.collect() 即可手动启动垃圾回收。例如import gcshow_memory_info(initial)a [i for i in range(10000000)]show_memory_info(after a created)del agc.collect()show_memory_info(finish)print(a)输出结果为initial memory used: 48.1015625 MBafter a created memory used: 434.3828125 MBfinish memory used: 48.33203125 MBNameError Traceback (most recent call last) in 11 12 show_memory_info(finish)--- 13 print(a)NameError: name a is not defined引用次数为 0 是垃圾回收启动的充要条件吗还有没有其他可能性呢其实引用计数是其中最简单的实现引用计数并非充要条件它只能算作充分非必要条件至于其他的可能性下面所讲的循环引用正是其中一种。循环引用首先思考一个问题如果有两个对象之间互相引用且不再被别的对象所引用那么它们应该被垃圾回收吗举个例子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: 47.984375 MBafter a, b created memory used: 822.73828125 MBfinished memory used: 821.73046875 MB程序中a 和 b 互相引用并且作为局部变量在函数 func 调用结束后a 和 b 这两个指针从程序意义上已经不存在但从输出结果中看到依然有内存占用这是为什么呢因为互相引用导致它们的引用数都不为 0。试想一下如果这段代码出现在生产环境中哪怕 a 和 b 一开始占用的空间不是很大但经过长时间运行后Python 所占用的内存一定会变得越来越大最终撑爆服务器后果不堪设想。有读者可能会说互相引用还是很容易被发现的呀问题不大。可是更隐蔽的情况是出现一个引用环在工程代码比较复杂的情况下引用环真不一定能被轻易发现。那么应该怎么做呢事实上Python 本身能够处理这种情况前面刚刚讲过可以显式调用 gc.collect() 来启动垃圾回收例如import gcdef 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()gc.collect()show_memory_info(finished)输出结果为initial memory used: 49.51171875 MBafter a, b created memory used: 824.1328125 MBfinished memory used: 49.98046875 MB事实上Python 使用标记清除(mark-sweep)算法和分代收集(generational)来启用针对循环引用的自动垃圾回收。当然每次都遍历全图对于 Python 而言是一种巨大的性能浪费。所以在 Python 的垃圾回收实现中标记清除算法使用双向链表维护了一个数据结构并且只考虑容器类的对象(只有容器类对象才有可能产生循环引用)。而分代收集算法则是将 Python 中的所有对象分为三代。刚刚创立的对象是第 0 代经过一次垃圾回收后依然存在的对象便会依次从上一代挪到下一代。而每一代启动自动垃圾回收的阈值则是可以单独指定的。当垃圾回收器中新增对象减去删除对象达到相应的阈值时就会对这一代对象启动垃圾回收。事实上分代收集基于的思想是新生的对象更有可能被垃圾回收而存活更久的对象也有更高的概率继续存活。因此通过这种做法可以节约不少计算量从而提高 Python 的性能。