2015-09-14 30 views
3

我剛剛發現了可能會在PHP中泄漏內存的難題。我在循環中運行一些代碼,在每個循環之後,內存使用量會增加,直到腳本達到內存限制。我已經確信:如何避免PHP 5.4中的內存泄漏?

  • 沒有全局變量(我相信沒有任何靜態)
  • 我運行PHP 5.4據稱具有循環引用
  • 我所有的變量,這個花哨的新垃圾收集器走出去的範圍內的每個週期
  • 我每個週期

這是在考慮到演示問題的示例腳本以後打電話gc_collect_cycles()後:

require_once(__DIR__ . '/libraries/PHPExcel/PHPExcel.php'); 
ini_set('memory_limit', '200M'); 
@mkdir(__DIR__ . '/output'); 
gc_enable(); 

for ($n = 0 ; $n < 10 ; $n++) 
{ 
    do_it($n); 
    gc_collect_cycles(); 
} 

function do_it($n) 
{ 
    echo 'Round '.$n.'...'; 

    $text = str_repeat('x', 50000); 

    $phpexcel = new PHPExcel(); 
    $worksheet = $phpexcel->getActiveSheet(); 

    for ($r = 1 ; $r < 50 ; $r++) 
     for ($c = ord('A') ; $c <= ord('S') ; $c++) 
      $worksheet->setCellValueExplicit(chr($c) . $r, $text, PHPExcel_Cell_DataType::TYPE_STRING); 

    // $phpexcel->disconnectWorksheets(); 

    unset($phpexcel, $worksheet); 

    echo 'done, now using ' . round((memory_get_usage())/1024/1024).' MB' . "\n"; 
} 

輸出:

Round 0...done, now using 41 MB 
Round 1...done, now using 80 MB 
Round 2...done, now using 123 MB 
Round 3...done, now using 157 MB 
Round 4... 
Fatal error: Allowed memory size of 209715200 bytes exhausted (tried to allocate 36 bytes) 

現在對於這個特定的問題the solution is每個週期,這取消設置一些對象成員之後調用$phpexcel->disconnectWorksheets();

真正的問題是:作爲一名PHP程序員,我該怎麼做才能避免這種內存泄漏?在我可以取消設置對象之前,是否真的必須遞歸遍歷每個對象以取消其成員的設置?

+1

我認爲這個問題可以更準確地概括爲「什麼是PHPExcel阻止循環引用垃圾回收器完成其工作?」。他們清楚地知道這是一個問題,因爲他們首先創建了'PHPExcel :: disconnectWorksheets()'函數(儘管這可能早於PHP 5.3中的新垃圾回收器)。通常我會說這會在他們的論壇/郵件列表中得到更好的回答,但他們似乎也沒有。 – Phylogenesis

+0

@Phylogenesis這是正確的,問題是或多或少「我能做些什麼來攪亂垃圾收集器?」所以我可以避免它。 – AndreKR

+0

我用'phpexcel'標記了這個問題,希望@MarkBaker會來解釋他的想法。 :) – AndreKR

回答

2

這裏的問題是靜態數組PHPExcel_Calculation::$_workbookSets獲取對每個工作簿的PHPExcel_Calculation對象的引用。每次運行do_it()都會增長。因爲這些物體從來沒有超出範圍,所以它們的記憶以及它們的屬性等都不能被回收。

替換您unset(...);PHPExcel_Calculation::unsetInstance($phpexcel);和內存泄漏消失了,因爲這將刪除該陣列關聯的對象(和做這一點。)

對一般問題:循環引用是不是問題,垃圾收集器可以很好地處理它們 - 避免使用全局變量(靜態變量只是奇特的全局變量),因爲它們可以很好地隱藏並且無法控制。

+0

現貨! @MarkBaker https://github.com/PHPOffice/PHPExcel/pull/674 – AndreKR

0

disconnectWorksheets()方法添加了預約PHP 5.3的花哨的新垃圾回收。

問題是PHPExcel有循環引用。 PHPExcel對象引用其工作表對象,而各個工作表引用它們的父PHPExcel對象。同樣,工作表對象引用其所有單元(通過緩存的單元集合),並且單元都引用其父工作表。

這種類型的循環關係無法使用舊的PHP垃圾收集器清理,該收集器完全脫離引用計數;這意味着如果任何對他們的引用都存在於其他地方,則不能解除對象。

disconnectWorksheets()提供打破從底部到頂部的那些環狀的關係的簡化的方法,包括:從他們的工作表的父斷開細胞,使只有非環狀工作表 - >細胞關係存在,並且PHPExcel對象及其工作表之間類似。

一旦循環關係被破壞,一個簡單的unset()應該工作。

不過,我從你正在創建一個單獨的參考工作表中的代碼中看到:

$worksheet = $phpexcel->getActiveSheet(); 

所以這個引用將不會被調用來清潔,以disconnectWorksheets(),並以PHPExcel參考對象也將保留在作者身上。

我懷疑那麼它可能會降下來,其中

unset($phpexcel, $worksheet, $writer); 

會取消對象的順序。

如果它首先嚐試取消設置$phpexcel,那麼它可能不能,因爲在$ worksheet中仍然存在對它的引用,而在$ writer中有另一個引用....可能會顛倒您未設置的實體的順序將這種差異

unset($writer, $worksheet, $phpexcel); 

或可能解封$writer$worksheet調用disconnectWorksheets()前;並且在此之後只需要設置$ phpexcel。


從理論上講,PHP 5.3的新的垃圾回收應該處理這些循環引用,但在實踐中,我不知道它確實是多麼有效的...這不是我在所有測試。理論上,它應該消除所有需要使用disconnectWorksheets()方法。

對於仍然使用PHP < = 5.3(PHP 5.2.0仍然是最早支持的版本;並且信不信由你,我仍然有人要我解決運行中的問題)的方法確實保留了disconnectWorksheets()方法就在這個週末的5.1.16下的PHPExcel)。但是,很可能有其他的循環引用(可能在樣式關係中),這些引用沒有通過調用disconnectWorksheets()來清除,所以我甚至不能保證這一點;但這是我能提供的最好建議。