Python 為開發者提供了許多便利,其中最大的便利之一是其幾乎無憂的記憶體管理。開發者無需手動為 Python 中的物件和資料結構分配、追蹤和釋放記憶體。運行時會為你完成所有這些工作,因此你可以專注於解決實際問題,而不是爭論機器級細節。
儘管如此,即使是經驗不多的 Python 用戶,了解 Python 的垃圾收集和記憶體管理是如何運作的也是有好處的。了解這些機制將幫助你避免更複雜的專案可能出現的效能問題。你也可以使用 Python 的內建工具來監控程式的記憶體管理行為。
每個 Python 物件都有一個引用計數,也稱為引用計數。 refcount 是持有對給定物件的引用的其他物件總數的計數。當你新增或刪除對物件的引用時,數字會上升或下降。當一個物件的參考計數變為零時,該物件將被釋放並釋放其記憶體。
什麼是參考?允許透過名稱或透過另一個物件中的存取器存取物件的任何內容。
這是一個簡單的例子:
x = "Hello there"
當我們向Python 發出這個命令時,引擎蓋下會發生兩件事:
如果我們說y = x,那麼引用計數將再次提高到 2。
每當xandy超出範圍或從它們的命名空間中刪除時,對於每個名稱,字串的參考計數都會減少 1。一旦x和y都超出範圍或被刪除,字串的引用計數變為 0 並被刪除。
現在,假設我們創建了一個包含字串的列表,如下所示:
x = ["Hello there", 2, False]
字串保留在記憶體中,直到列表本身被刪除或包含字串的元素從列表中刪除。這些操作中的任何一個都將導致唯一持有對字串的參考的事物消失。
現在考慮這個範例:
x = "Hello there" y = [x]
如果我們從 中刪除第一個元素y,或完全刪除清單y,則字串仍在記憶體中。這是因為名稱x包含對它的引用。
在大多數情況下,引用計數工作正常。但有時你會遇到兩個物件各自持有對彼此的引用的情況。這稱為 參考週期。在這種情況下,物件的引用計數永遠不會達到零,也永遠不會從記憶體中刪除。
這是一個人為的例子:
x = SomeClass() y = SomeOtherClass() x.item = y y.item = x
由於x並y持有彼此的引用,因此它們永遠不會從系統中刪除——即使沒有其他任何東西引用它們中的任何一個。
Python 自己的運行時為物件產生參考循環其實是相當普遍的。一個範例是帶有包含對異常本身的參考的回溯物件的異常。
在Python的早期版本中,這是一個問題。具有引用週期的物件可能會隨著時間的推移而累積,這對於長時間運行的應用程式來說是一個大問題。但 Python 此後引入了循環檢測和垃圾收集系統,用於管理引用循環。
Python 的垃圾收集器會偵測具有引用週期的物件。它透過追蹤作為「容器」的物件(例如列表、字典、自訂類別實例)並確定其中的哪些物件無法在其他任何地方存取來實現這一點。
一旦這些物件被挑選出來,垃圾收集器就會透過確保它們的參考計數可以安全地降為零來刪除它們。
絕大多數 Python 物件沒有引用週期,因此垃圾收集器不需要 24/7 運行。相反,垃圾收集器使用一些啟發式方法來減少運行頻率,並且每次都盡可能有效地運行。
當 Python 解釋器啟動時,它會追蹤已指派但未釋放的物件數量。絕大多數 Python 物件的生命週期都很短,因此它們會迅速出現和消失。但隨著時間的推移,更多長壽的物體會出現。一旦超過一定數量的此類物件堆積起來,垃圾收集器就會運作。
每次垃圾收集器運行時,它都會收集所有在收集中倖存下來的對象,並將它們放在一個稱為一代的群組中。這些「第一代」物件在參考週期中被掃描的頻率較低。任何在垃圾收集器中倖存下來的第一代物件最終都會遷移到第二代,在那裡它們被掃描得更少。
同样,垃圾收集器不会跟踪所有内容。例如,像用户创建的类这样的复杂对象总是被跟踪。但是不会跟踪仅包含简单对象(如整数和字符串)的字典,因为该特定字典中的任何对象都不会包含对其他对象的引用。不能保存对其他元素(如整数和字符串)的引用的简单对象永远不会被跟踪。
通常,垃圾收集器不需要调整即可运行良好。Python 的开发团队选择了反映最常见现实世界场景的默认值。但是如果你确实需要调整垃圾收集的工作方式,你可以使用Python 的 gc 模块。该gc模块为垃圾收集器的行为提供编程接口,并提供对正在跟踪的对象的可见性。
gc当你确定不需要垃圾收集器时,你可以做的一件有用的事情是关闭它。例如,如果你有一个堆放大量对象的短运行脚本,则不需要垃圾收集器。脚本结束时,所有内容都将被清除。为此,你可以使用命令禁用垃圾收集器gc.disable()。稍后,你可以使用 重新启用它gc.enable()。
你还可以使用 手动运行收集周期gc.collect()。一个常见的应用是管理程序的性能密集型部分,该部分会生成许多临时对象。你可以在程序的该部分禁用垃圾收集,然后在最后手动运行收集并重新启用收集。
另一个有用的垃圾收集优化是gc.freeze(). 发出此命令时,垃圾收集器当前跟踪的所有内容都被“冻结”,或者被列为免于将来的收集扫描。这样,未来的扫描可以跳过这些对象。如果你有一个程序在启动之前导入库并设置大量内部状态,那么你可以gc.freeze()在所有工作完成后发出。这使垃圾收集器不必搜寻那些无论如何都不太可能被删除的东西。(如果你想对冻结的对象再次执行垃圾收集,请使用gc.unfreeze().)
你还可以使用它gc来调试垃圾收集行为。如果你有过多的对象堆积在内存中并且没有被垃圾收集,你可以使用gc's 检查工具来找出可能持有对这些对象的引用的对象。
如果你想知道哪些对象持有对给定对象的引用,可以使用gc.get_referrers(obj)列出它们。你还可以使用gc.get_referents(obj)来查找给定对象引用的任何对象。
如果你不确定给定对象是否是垃圾收集的候选对象,gc.is_tracked(obj)请告诉你垃圾收集器是否跟踪该对象。如前所述,请记住垃圾收集器不会跟踪“原子”对象(例如整数)或仅包含原子对象的元素。
如果你想亲自查看正在收集哪些对象,可以使用 设置垃圾收集器的调试标志gc.set_debug(gc.DEBUG_LEAK|gc.DEBUG_STATS)。这会将有关垃圾收集的信息写入stderr。它将所有作为垃圾收集的对象保留在只读列表中。
如前所述,如果你在某处仍有对它们的引用,则对象可能会堆积在内存中而不会被收集。这并不是 Python 垃圾收集本身的失败。垃圾收集器无法判断你是否不小心保留了对某物的引用。
让我们以一些防止对象永远不会被收集的指针作为结尾。
如果你将对象 1 指定为对象 2 的属性(例如类),则对象 2 将需要超出范围,然后对象 1 才会:
obj1 = MyClass() obj2.prop = obj1
更重要的是,如果这种情况发生在某种其他操作的副作用中,例如将对象 2 作为参数传递给对象 1 的构造函数,你可能不会意识到对象 1 持有一个引用:
obj1 = MyClass(obj2)
另一个例子:如果你将一个对象推入模块级列表并忘记该列表,则该对象将一直保留,直到从列表中删除,或者直到列表本身不再有任何引用。但是如果该列表是一个模块级对象,它可能会一直存在,直到程序终止。
简而言之,请注意你的对象可能被另一个看起来并不总是很明显的对象持有的方式。
Python 的 weakref 模块允许你创建对其他对象的弱引用。弱引用不会增加对象的引用计数,因此只有弱引用的对象是垃圾回收的候选对象。
一个常见的用途weakref是对象缓存。你不希望仅仅因为它具有缓存条目而保留引用的对象,因此你将 aweakref用于缓存条目。
最後,如果你知道給定物件包含對另一個物件的引用,你總是可以手動中斷對該物件的參考。例如,如果你有instance_of_class.ref = other_object,你可以設定instance_of_class.ref = None何時準備刪除 instance_of_class。
透過了解 Python 記憶體管理的工作原理,我們對其垃圾收集系統如何幫助優化 Python 程式中的內存,以及如何使用標準庫和其他地方提供的模組來控制記憶體使用和垃圾收集。
原文標題:Python garbage collection and the gc module
以上是Python 記憶體管理的工作原理,你了解嗎?的詳細內容。更多資訊請關注PHP中文網其他相關文章!