亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

Python 的內存管理與垃圾回收

1. 內存管理概述

1.1 手動內存管理

在計算機發展的早期,編程語言提供了手動內存管理的機制,例如 C 語言,提供了用于分配和釋放的函數 malloc 和 free,如下所示:

#include <stdlib.h>

void *malloc(size_t size);
void free(void *p);
  • 函數 malloc 分配指定大小 size 的內存,返回內存的首地址
  • 函數 free 釋放之前申請的內存

程序員負責保證內存管理的正確性:使用 malloc 申請一塊內存后,如果不再使用,需要使用 free 將其釋放,示例如下:

#include <stdlib.h>

void test()
{
    void *p = malloc(10);

    訪問 p 指向的內存區域;

    free(p);
}

int main()
{
    test();
}
  • 使用 malloc(10) 分配一塊大小為 10 個字節的內存區域
  • 使用 free§ 釋放這塊內存區域

如果忘記釋放之前使用 malloc 申請的內存,則會導致可用內存不斷減少,這種現象被稱為 “內存泄漏”,示例如下:

#include <stdio.h>
#include <stdlib.h>

void test()
{
    void *p = malloc(10);

    訪問 p 指向的內存區域;
}

int main()
{
    while (1)
        test();
}
  • 在函數 test 中,使用 malloc 申請一塊內存
    • 但是使用完畢后,忘記釋放了這塊內存
  • 在函數 main 中,循環調用函數 test()
    • 每次調用函數 test(),都會造成內存泄漏
    • 最終,會耗盡所有的內存

1.2 自動內存管理

在計算機發展的早期,硬件性能很差,為了最大程度的壓榨硬件性能,編程語言提供了手動管理內存的機制。手動管理內存的機制的優點在于能夠有效規劃和利用內存,其缺點在于太繁瑣了,很容易出錯。

隨著計算機的發展,硬件性能不斷提高,這時候出現的編程語言,例如:Java、C#、PHP、Python,則提供了自動管理內存的機制:程序員申請內存后,不需要再顯式的釋放內存,由編程語言的解釋器負責釋放內存,從根本上杜絕了 “內存泄漏” 這類錯誤。

在下面的 Python 程序中,在無限循環中不斷的申請內存:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

while True:
    person = Person('tom', 13)
  • 類 Person 包含兩個屬性:name 和 age
  • 在 while 循環中,使用類 Person 生成一個實例 person
    • 需要申請一塊內存用于保存實例 person 的屬性

Python 解釋器運行這個程序時,發現實例 person 不再被引用后,會自動的釋放 person 占用的空間。因此這個程序可以永遠的運行下去,而不會把內存耗盡。

2. 基于引用計數的內存管理

2.1 基本原理

引用計數是一種最簡單的自動內存管理機制:

  • 每個對象都有一個引用計數
  • 當把該對象賦值給一個變量時,對象的引用計數遞增 1

引用計數的實例如下:

A = object()
B = A
A = None
B = None
  • 在第 1 行,使用 object() 創建一個對象,變量 A 指向該對象
    • 對象的引用計數變化為 1
  • 在第 2 行,變量 B 指向相同的對象
    • 對象的引用計數變化為 2
  • 在第 3 行,變量 A 指向 None
    • 對象的引用計數變化為 1
  • 在第 3 行,變量 B 指向 None
    • 對象的引用計數變化為 0

引用計數

從圖中可以看出,當變量 A 和變量 B 都不再指向對象時,對象的引用計數變為 0,系統檢測到該對象成為廢棄對象,可以將此廢棄對象回收。

2.2 優點和缺點

引用計數的優點在于:

  • 實現簡單
  • 系統檢測到對象的引用計數變為 0 后,可以及時的釋放廢棄的對象
  • 處理回收內存的時間分攤到了平時

引用計數的缺點在于:

  • 維護引用計數消耗性能,每次變量賦值時,都需要維護維護引用計數
  • 無法釋放存在循環引用的對象

下面是一個存在循環引用的例子:

class Node:
    def __init__(self, data, next):
        self.data = data
        self.next = next

node = Node(123, None)
node.next = node
node = None
  • 在第 6 行,創建對象 node
    • 對象 node 的 next 指向 None
    • 此時對象 node 的引用計數為 1
  • 在第 7 行,對象 node 的 next 指向 node 自身
    • 此時對象 node 的引用計數為 2
  • 在第 7 行,對象 node 指向 None
    • 此時對象 node 的引用計數為 1

對象 node 的 next 字段指向自身,導致:即使沒有外部的變量指向對象 node,對象 node 的引用計數也不會變為 0,因此對象 node 就永遠不會被釋放了

3. 基于垃圾回收的內存管理

3.1 基本原理

垃圾回收是目前主流的內存管理機制:

  • 通過一系列的稱為 “GC Root” 的對象作為起始對象
  • 從 GC Root 出發,進行遍歷
  • 最終將對象劃分為兩類:
    • 從 GC Root 可以到達的對象
    • 從 GC Root 無法到達的對象

從 GC Root 無法到達的對象被認為是廢棄對象,可以被系統回收。

垃圾回收

  • 在 Python 語言中,可作為 GC Roots 的對象主要是指全局變量指向的對象。
  • 從 GC Roots 出發,可以到達 object 1、object 2、object 3、object 4
  • 從 GC Roots 出發,無法到達 object 5、object 6、object 7,它們被判定為可回收的對象

3.2 優點和缺點

垃圾回收的優點在于:

  • 可以處理存在循環引用的對象

垃圾回收的缺點在于:

  • 實現復雜
  • 進行垃圾回收時,需要掃描程序中所有的對象,因此需要暫停程序的運行。當程序中對象數量較多時,暫停程序的運行時間過長,系統會有明顯的卡頓現象。

4. Python 的內存管理機制

Python 的內存管理采用了混合的方法:

  • Python 使用引用計數來保持追蹤內存中的對象,當對象的引用計數為 0 時,回收該對象
  • Python 同時使用垃圾回收機制來回收存在有循環引用的對象

下面的例子中,演示了 Python 的內存管理策略:

class Circular:
    def __init__(self):
        self.data = 0
        self.next = self

class NonCircular:
    def __init__(self):
        self.data = 0
        self.next = None

def hybrid():
    while True:
        circular = Circular()
        nonCircular = NonCircular()

hybrid()
  • 類 Circular,創建了一個包含循環引用的對象
    • self.next 指向自身,導致了循環引用
    • 類 Circular 的實例只能被垃圾回收機制釋放
  • 類 NonCircular,創建了一個不包含循環引用的對象
    • self.next 指向 None,沒有循環引用
    • 類 NonCircular 的實例可以引用計數機制釋放
  • 在方法 hybrid 中
    • 在無限循環中,不斷的申請 Circular 實例和 NonCircular 實例

通過引用計數和垃圾回收機制,內存不會被耗盡,程序可以永遠的運行下去。