JavaScript memory leaks, czyli utracona pamięć (cz. 2)


kamil avatar Kamil Wiśniewski 12 Września 2017 1 21351 Share:
Druga część poradnika o usuwaniu wycieków pamięci w aplikacjach JavaScript.

Pierwszą część znajdziesz tutaj:
JavaScript memory leaks, czyli utracona pamięć

Czy mam problem z pamięcią?

Jak sprawdzić czy aplikacja gubi pamięć? Jest kilka szybkich sposobów, które pozwolą nam wstępnie postawić diagnozę. Szczególnie łatwo zorientujemy się jeśli wycieki pamięci są znaczne.
Naturalnie pierwszą rzeczą, która powinna zwrócić naszą uwagę jest samo zachowanie aplikacji. Jeśli aplikacja spowalnia w miarę działania, wydłużają się czasy reakcji systemu lub w jakiś inny sposób zachowuje się coraz mniej stabilnie im dłużej jest uruchomiona, wówczas powinna zapalić nam się czerwona lampka.
Doskonałych narzędzi do diagnozy problemów z wyciekami pamięci dostarcza np. Chrome Dev Tools.
Szybką odpowiedź na pytanie o sam fakt istnienia problemu często może dać obserwacja wykresu alokowanej pamięci. W Chrome Dev Tools, w zakładce Memory znajdziemy opcję „Record Allocation Timeline”. Zaznaczamy ją, klikamy „start”, wykonujemy operacje na aplikacji, które wzbudzają nasze największe podejrzenia (np. te, które wiążą się z alokowaniem i zwalnianiem największej ilości pamięci), przywracamy aplikację do punktu wyjściowego i na koniec klikamy „stop”, żeby Chrome skończył rejestrowanie wykresu. Jeśli z każdym kolejnym powtórzeniem operacji wykres rośnie (charakterystyczny jest wykres schodkowy lub „ząbkowy”), może to być pierwszym potencjalnie złym objawem. Najważniejsze jednak będzie to, czy w określonych, tych samych stanach aplikacji poziomy użycia pamięci (a więc poziomy wykresu) również są zbliżone. Świadomie używam tu słowa „zbliżone”, ponieważ pewne różnice mogą być naturalne. A więc, jeśli rozpoczęliśmy rejestrowanie wykresu, gdy aplikacja znajdowała się w określonym stanie i na koniec powróciliśmy do tego samego stanu, ale poziom użycia pamięci na koniec jest wyższy niż był na początku (wykres jest powyżej początkowego poziomu) to najprawdopodobniej oznacza to jakieś wycieki.

Z wykresu alokacji nie dowiemy się jednak gdzie jest i na czym dokładnie polega problem. Znacznie dokładniejsze odpowiedzi da nam obserwacja stosu i to właśnie ten sposób sprawdza się dla mnie najlepiej.

Jak odzyskać pamięć?

A zatem jak odnaleźć i wyeliminować wycieki pamięci w aplikacji JavaScript? Oto przepis, który sam stosuję:

  1. Otwórz aplikację i przejdź do stanu początkowego (za chwilę to sobie wyjaśnimy). Nazwijmy go stanem A.
  2. Otwórz narzędzia Dev Tools i przejdź do zakładki „Memory„. Wybierz opcję „Take Heap Snapshot„.
  3. Wykonaj Snapshot (nr. 1).
  4. Teraz wykonaj na aplikacji operację, którą chcesz przetestować pod kątem wycieków pamięci (np. załadowanie jakiegoś widoku). Przyjmijmy, że po tej operacji aplikacja znajduje się w stanie B.
  5. Wykonaj kolejny Snapshot (nr. 2).
  6. Przywróć aplikację do stanu A z punktu 1 (np. przechodząc z powrotem do poprzedniego widoku).
  7. Wykonaj Snapshot nr. 3.
  8. Teraz wybierz Snapshot nr. 3 i z rozwijanej listy zamiast domyślnie zaznaczonego „All objects” wybierz opcję „Objects allocated between Snapshot 1 and Snapshot 2”

Mówiąc o stanie początkowym, mam na myśli stan, z którego możesz bezpośrednio wykonać operację, którą zamierzasz przetestować pod kątem wycieków. A więc jeśli operacją testowaną na wycieki pamięci jest wyświetlenie widoku B, przejdź do dowolnego innego widoku (A), z którego masz możliwość bezpośredniego przejścia do B.
Oczywiście wszystko zależy od tego jak działa Twoja aplikacja, niemniej zasada pozostaje podobna. Operacją z punktów 4 i 6 może być przechodzenie pomiędzy określonymi stronami aplikacji lub np. wyłączenie i włączenie całej aplikacji, gdy daje ona taką możliwość.

Ale co właściwie zrobiliśmy powyżej?

Warto wiedzieć, że wykonując Snapshot stosu przeglądarka najpierw uruchamia Garbage Collector. A zatem użyta wcześniej pamięć, która nie jest już wykorzystywana powinna być na tym etapie zwolniona.

Warto też wiedzieć, że często rozmiar pierwszego snapshot’a może być odrobinę większy od pozostałych i nie koniecznie jest to dla nas powód do zmartwień. Natomiast kolejne snapshoty wykonywane w tych samych stanach aplikacji powinny już mieć te same rozmiary. Dla eksperymentu możesz więc wykonać dowolną liczbę powtórzeń punktów 4-7 i porównywać ze sobą rozmiary co drugich snapshotów. Ale wróćmy do naszego wyniku.

Wyświetlając obiekty zaalokowane pomiędzy snapshotem 1 i 2, wyświetlamy te obiekty, których alokacja była niezbędna do wyświetlenia widoku B. Ale przecież… Jesteśmy w widoku A. A zatem obiekty zaalokowane między snapshotami 1 i 2, które są nadal obecne w snapshocie 3 to nasi podejrzani.

Na liście znajdują się obiekty pogrupowane według typów. Rozwijając grupę zobaczymy listę konkretnych obiektów danego typu. Ich zidentyfikowanie nie zawsze będzie jednak łatwe. Czasem łatwo zidentyfikujemy obiekt po samym jego typie, o ile jest on specyficzny. Gorzej jeżeli jest to po prostu typ „Object” czy „Array”. Wówczas w identyfikacji mogą pomóc nam dwa elementy.
Po najechaniu kursorem na dany obiekt wyświetli nam się jego podgląd (czasem może być on niedostępny). Pola i metody mogą naprowadzić nas na trop. Z kolei po kliknięciu w wybrany obiekt w oknie poniżej wyświetli nam się jego graf. Może nam się więc udać zlokalizować obiekt na podstawie charakterystycznych obiektów w grafie.

Chrome oznacza niektóre elementy kolorami, co również bywa bardzo pomocne. Elementy oznaczone na czerwono to elementy odłączone z drzewa DOM, czyli takie, do których istnieją referencje, ale nie należą do DOM. Najczęściej takie elementy same w sobie nie są przyczyną wycieków, a raczej tylko ich symptomem. Mogą więc być wskazówką do poszukiwań, ale przyczyny powinniśmy szukać gdzie indziej. Inne elementy są oznaczone na żółto – to elementy, do których istnieją bezpośrednie referencje z poziomu JavaScript. I to te elementy najczęściej doprowadzą nas do przyczyny wycieków, więc warto poświęcić im szczególną uwagę.

Powodzenia i dbajcie o pamięć!

Komentarze

Nie ma jeszcze żadnych komentarzy. Skomentuj jako pierwszy!

Twój komentarzTwój adres email nie zostanie opublikowany

Nasza strona używa ciasteczek na wypadek gdybyś zgłodniał w trakcie jej przeglądania. Nie masz nic przeciwko? Pewnie, nie ma problemu!