傳統上,像以前的 php 用到的引用計數內存機制,無(wú)法處理循環(huán)的引用內存泄漏。然而 5.3.0 PHP 使用文章? 引用計數系統中的同步周期回收(Concurrent Cycle Collection in Reference Counted Systems)中的同步算法,來(lái)處理這個(gè)內存泄漏問(wèn)題。
對算法的完全說(shuō)明有點(diǎn)超出這部分內容的范圍,將只介紹其中基礎部分。首先,我們先要建立一些基本規則,如果一個(gè)引用計數增加,它將繼續被使用,當然就不再在垃圾中。如果引用計數減少到零,所在變量容器將被清除(free)。就是說(shuō),僅僅在引用計數減少到非零值時(shí),才會(huì )產(chǎn)生垃圾周期(garbage cycle)。其次,在一個(gè)垃圾周期中,通過(guò)檢查引用計數是否減1,并且檢查哪些變量容器的引用次數是零,來(lái)發(fā)現哪部分是垃圾。
為避免不得不檢查所有引用計數可能減少的垃圾周期,這個(gè)算法把所有可能根(possible roots 都是zval變量容器),放在根緩沖區(root buffer)中(用紫色來(lái)標記,稱(chēng)為疑似垃圾),這樣可以同時(shí)確保每個(gè)可能的垃圾根(possible garbage root)在緩沖區中只出現一次。僅僅在根緩沖區滿(mǎn)了時(shí),才對緩沖區內部所有不同的變量容器執行垃圾回收操作??瓷蠄D的步驟 A。
在步驟 B 中,模擬刪除每個(gè)紫色變量。模擬刪除時(shí)可能將不是紫色的普通變量引用數減"1",如果某個(gè)普通變量引用計數變成0了,就對這個(gè)普通變量再做一次模擬刪除。每個(gè)變量只能被模擬刪除一次,模擬刪除后標記為灰(原文說(shuō)確保不會(huì )對同一個(gè)變量容器減兩次"1",不對的吧)。
在步驟 C 中,模擬恢復每個(gè)紫色變量?;謴褪怯袟l件的,當變量的引用計數大于0時(shí)才對其做模擬恢復。同樣每個(gè)變量只能恢復一次,恢復后標記為黑,基本就是步驟 B 的逆運算。這樣剩下的一堆沒(méi)能恢復的就是該刪除的藍色節點(diǎn)了,在步驟 D 中遍歷出來(lái)真的刪除掉。
算法中都是模擬刪除、模擬恢復、真的刪除,都使用簡(jiǎn)單的遍歷即可(最典型的深搜遍歷)。復雜度為執行模擬操作的節點(diǎn)數正相關(guān),不只是紫色的那些疑似垃圾變量。
現在,你已經(jīng)對這個(gè)算法有了基本了解,我們回頭來(lái)看這個(gè)如何與PHP集成。默認的,PHP的垃圾回收機制是打開(kāi)的,然后有個(gè) php.ini 設置允許你修改它:zend.enable_gc。
當垃圾回收機制打開(kāi)時(shí),每當根緩存區存滿(mǎn)時(shí),就會(huì )執行上面描述的循環(huán)查找算法。根緩存區有固定的大小,可存10,000個(gè)可能根,當然你可以通過(guò)修改PHP源碼文件Zend/zend_gc.c
中的常量GC_ROOT_BUFFER_MAX_ENTRIES
,然后重新編譯PHP,來(lái)修改這個(gè)10,000值。當垃圾回收機制關(guān)閉時(shí),循環(huán)查找算法永不執行,然而,可能根將一直存在根緩沖區中,不管在配置中垃圾回收機制是否激活。
當垃圾回收機制關(guān)閉時(shí),如果根緩沖區存滿(mǎn)了可能根,更多的可能根顯然不會(huì )被記錄。那些沒(méi)被記錄的可能根,將不會(huì )被這個(gè)算法來(lái)分析處理。如果他們是循環(huán)引用周期的一部分,將永不能被清除進(jìn)而導致內存泄漏。
即使在垃圾回收機制不可用時(shí),可能根也被記錄的原因是,相對于每次找到可能根后檢查垃圾回收機制是否打開(kāi)而言,記錄可能根的操作更快。不過(guò)垃圾回收和分析機制本身要耗不少時(shí)間。
除了修改配置zend.enable_gc,也能通過(guò)分別調用gc_enable() 和 gc_disable()函數來(lái)打開(kāi)和關(guān)閉垃圾回收機制。調用這些函數,與修改配置項來(lái)打開(kāi)或關(guān)閉垃圾回收機制的效果是一樣的。即使在可能根緩沖區還沒(méi)滿(mǎn)時(shí),也能強制執行周期回收。你能調用gc_collect_cycles()函數達到這個(gè)目的。這個(gè)函數將返回使用這個(gè)算法回收的周期數。
允許打開(kāi)和關(guān)閉垃圾回收機制并且允許自主的初始化的原因,是由于你的應用程序的某部分可能是高時(shí)效性的。在這種情況下,你可能不想使用垃圾回收機制。當然,對你的應用程序的某部分關(guān)閉垃圾回收機制,是在冒著(zhù)可能內存泄漏的風(fēng)險,因為一些可能根也許存不進(jìn)有限的根緩沖區。因此,就在你調用gc_disable()函數釋放內存之前,先調用gc_collect_cycles()函數可能比較明智。因為這將清除已存放在根緩沖區中的所有可能根,然后在垃圾回收機制被關(guān)閉時(shí),可留下空緩沖區以有更多空間存儲可能根。