W Perl pamięć dla zmiennych i struktur danych zarządzana jest za pomocą mechanizmu liczenia odniesień (reference counting). Obiekt lub struktura jest zwalniana, gdy licznik odniesień spada do zera.
Jednak ten mechanizm NIE bada cyklicznych odniesień. Jeśli obiekty odwołują się do siebie nawzajem, ich liczniki nie osiągają zera, a pamięć nie jest zwalniana.
Do czyszczenia cyklicznych odniesień używa się:
Scalar::Util::weaken. Słabe odniesienie nie zwiększa licznika odniesień, pozwalając GC zwolnić obiekt, jeśli nie ma innych silnych odniesień.Przykład tworzenia słabego odniesienia:
use Scalar::Util 'weaken'; my $parent = {}; my $child = { parent => $parent }; $parent->{child} = $child; weaken($child->{parent}); # teraz parent->child->parent nie przeszkadza w czyszczeniu
Czy Perl może sam "rozpoznać" i zwolnić cykliczne odniesienia bez modułu Scalar::Util lub ręcznej interwencji?
Nie, Perl domyślnie nie potrafi automatycznie "zbierać" cyklicznych struktur, ponieważ ten mechanizm wymaga analizy struktury grafu obiektów (jak np. GC w JVM lub Pythonie). Tak więc zawsze należy dbać o czyszczenie cykli samodzielnie.
Historia
W serwerze przechowywania sesji dla aplikacji internetowej aktywnie używano obiektów User z dwukierunkowymi powiązaniami. Okazało się, że po kilku tysiącach odwołań pamięć procesu rosła z powodu cykli: $user->{session}->{user} = $user. Wyciek zniknął po wprowadzeniu weaken dla odwrotnych odniesień.
Historia
Przy używaniu pamięci podręcznej z algorytmem LRU przechowywano łańcuchy obiektów wzajemnie się odwołujących. Programiści nie przewidzieli ręcznego zerowania powiązań, co doprowadziło do gwałtownego wzrostu pamięci po kilku dniach działania usługi i awarii z powodu OOM.
Historia
W skomplikowanej mikroserwisie do przechowywania dokumentów przy generowaniu raportów używano eval i dużych struktur danych z cyklicznymi odniesieniami. Programiści liczyli na automatyczne zbieranie śmieci przez Perl, ale serwer "zapamiętywał" stare obiekty i po tygodniu pracy stracił całą dostępną RAM. Diagnoza ujawniła cykle i używała Scalar::Util po każdym raporcie.