Dowiedz się, jak zapisywać zrzuty stosu za pomocą opcji Pamięć > Profile > Zrzut stosu i znajdowanie wycieków pamięci.
Program profilujący sterty pokazuje rozkład pamięci według obiektów JavaScript na Twojej stronie i powiązanych węzłów DOM. Za jego pomocą możesz robić zrzuty stosu JS, analizować wykresy pamięci, porównywać zrzuty i znajdować wycieki pamięci. Więcej informacji znajdziesz w artykule Drzewo przechowywania obiektów.
Zrób zdjęcie
Aby zrobić zrzut stosu:
- Na stronie, którą chcesz profilować, otwórz Narzędzia deweloperskie i otwórz panel Pamięć.
- Wybierz typ profilowania radio_button_checked Zrzut sterty, a następnie wybierz instancję maszyny wirtualnej JavaScript i kliknij Wykonaj zrzut.
Gdy panel Pamięć wczytuje i analizuje zrzut, pod tytułem zrzutu w sekcji SNAPSHOTS pod tytułem zrzutu ekranu wyświetla się łączny rozmiar osiągalnych obiektów JavaScript.
Zrzuty pokazują tylko te obiekty z grafu pamięci, które są osiągalne z obiektu globalnego. Wykonywanie zrzutu zawsze rozpoczyna się od czyszczenia pamięci.
Wyczyść zrzuty
Aby usunąć wszystkie zrzuty, kliknij zablokuj Wyczyść wszystkie profile:
Wyświetl zrzuty
Aby przejrzeć migawki z różnych perspektyw w różnych celach, wybierz jeden z widoków z menu u góry:
Wyświetl | Treść | Cel |
---|---|---|
Podsumowanie | Obiekty pogrupowane według nazw konstruktorów. | Dzięki niemu możesz wyszukiwać obiekty i wykorzystywać ich pamięć na podstawie ich typu. Przydatne do śledzenia wycieków DOM. |
Porównanie | Różnice między dwoma zrzutami. | Służy do porównywania co najmniej 2 zrzutów przed operacją i po niej. Potwierdź obecność i przyczynę wycieku pamięci, sprawdzając wartość delta wolnej pamięci i liczby odwołań. |
izolacja | Zawartość sterty | Zapewnia lepszy widok struktury obiektów i pomaga analizować obiekty, do których odwołuje się globalna przestrzeń nazw (window), w celu ustalenia, co je chroni. Użyj go, aby przeanalizować zamknięcia i zająć się obiektami na bardzo ogólnym poziomie. |
Statystyki | Wykres kołowy alokacji pamięci | Sprawdzanie rzeczywistych rozmiarów części pamięci przydzielonych do kodu, ciągów znaków, tablic JS, tablic z typem i obiektów systemowych. |
Widok podsumowania
Początkowo w widoku Podsumowanie otwierany jest zrzut stosu z listą Konstruktorów w kolumnie. Możesz rozwijać konstruktory, aby zobaczyć obiekty, których instancja została utworzona.
Aby odfiltrować nieistotne konstruktory, wpisz nazwę, którą chcesz sprawdzić, w Filtrze zajęć u góry widoku Podsumowanie.
Liczby obok nazw konstruktorów wskazują łączną liczbę obiektów utworzonych przy użyciu tego konstruktora. Widok Podsumowanie zawiera też te kolumny:
- Odległość pokazuje odległość do pierwiastka przy użyciu najkrótszej prostej ścieżki węzłów.
- Płytki rozmiar pokazuje sumę płytkich rozmiarów wszystkich obiektów utworzonych przez określony konstruktor. Płytki rozmiar to rozmiar pamięci przechowywanej przez sam obiekt. Tablice i ciągi tekstowe mają zazwyczaj płytki rozmiar. Zobacz też Rozmiary obiektów.
- Zachowywany rozmiar pokazuje maksymalny rozmiar zachowany w tym samym zbiorze obiektów. Zachowany rozmiar to rozmiar pamięci, który można zwolnić przez usunięcie obiektu, przez co elementy zależne nie są już osiągalne. Zobacz też Rozmiary obiektów.
Po rozwinięciu konstruktora widok Podsumowanie pokazuje wszystkie jego instancje. W przypadku każdej instancji w odpowiednich kolumnach zobaczysz podział na jej płytki i zachowywany rozmiar. Liczba po znaku @
to unikalny identyfikator obiektu. Umożliwia porównywanie zrzutów sterty według poszczególnych obiektów.
Filtry konstruktora
Widok Podsumowanie umożliwia filtrowanie konstruktorów na podstawie typowych przypadków niewydajnego wykorzystania pamięci.
Aby użyć tych filtrów, w menu po prawej stronie na pasku działań wybierz jedną z tych opcji:
- Wszystkie obiekty: wszystkie obiekty przechwycone przez bieżący zrzut. Ustawienie domyślne.
- Obiekty przydzielone przed zrzutem 1: obiekty, które zostały utworzone i pozostały w pamięci przed wykonaniem pierwszego zrzutu.
- Obiekty alokowane między zrzutami 1 i zrzutami 2: wyświetla różnicę w obiektach między najnowszym a poprzednim zrzutem ekranu. Każdy nowy zrzut dodaje do listy przyrost wartości tego filtra.
- Zduplikowane ciągi: wartości ciągów znaków, które zostały zapisane wiele razy w pamięci.
- Obiekty przechowywane przez odłączone węzły: obiekty, które są utrzymywane, ponieważ odwołują się do nich odłączony węzeł DOM.
- Obiekty przechowywane przez konsolę Narzędzi deweloperskich: obiekty są przechowywane w pamięci, ponieważ były oceniane lub akceptowane za pomocą konsoli Narzędzi deweloperskich.
Wpisy specjalne w podsumowaniu
Oprócz grupowania według konstruktorów widok Podsumowanie grupuje też obiekty według:
- Wbudowane funkcje, np.
Array
lubObject
. - Elementy HTML pogrupowane według tagów, na przykład
<div>
,<a>
,<img>
i inne. - Funkcje zdefiniowane w kodzie.
- Kategorie specjalne, które nie są oparte na konstruktorach.
(array)
Ta kategoria zawiera różne obiekty tablicowe wewnętrznych, które nie odpowiadają bezpośrednio obiektom widocznym w JavaScripcie.
Na przykład zawartość obiektów JavaScript Array
jest przechowywana w dodatkowym obiekcie wewnętrznym o nazwie (object elements)[]
, co ułatwia zmianę rozmiaru. Podobnie nazwane właściwości w obiektach JavaScriptu są często przechowywane w dodatkowych obiektach wewnętrznych o nazwie (object properties)[]
, które są też wymienione w kategorii (array)
.
(compiled code)
Ta kategoria zawiera dane wewnętrzne, których V8 potrzebuje do uruchamiania funkcji zdefiniowanych przez JavaScript lub WebAssembly. Każdą funkcję można przedstawić na różne sposoby: od małej i wolnej do dużej i szybkiej.
V8 automatycznie zarządza wykorzystaniem pamięci w tej kategorii. Jeśli funkcja jest uruchamiana wiele razy, V8 wykorzystuje na potrzeby tej funkcji więcej pamięci, aby działać szybciej. Jeśli funkcja nie była uruchomiona od jakiegoś czasu, V8 może wyczyścić dla niej dane wewnętrzne.
(concatenated string)
Gdy V8 łączy 2 ciągi, jak w przypadku operatora +
JavaScriptu, może wewnętrznie przedstawić wynik jako „połączony ciąg znaków”. nazywa się też strukturą danych Rope.
Zamiast kopiować wszystkie znaki z dwóch ciągów źródłowych do nowego ciągu, V8 przypisuje niewielki obiekt z wewnętrznymi polami o nazwach first
i second
, które wskazują te dwa ciągi źródłowe. To pozwala V8 zaoszczędzić czas i pamięć. Z punktu widzenia kodu JavaScript są to zwykłe ciągi znaków, które działają jak każdy inny ciąg.
InternalNode
Ta kategoria reprezentuje obiekty przydzielone poza V8, takie jak obiekty C++ zdefiniowane przez Blink.
Aby wyświetlić nazwy klas C++, użyj Chrome for Testing i wykonaj te czynności:
- Otwórz Narzędzia deweloperskie i włącz ustawienia Ustawienia > Eksperymenty > check_box Pokaż opcję udostępniania zasobów wewnętrznych w zrzutach sterty.
- Otwórz panel Pamięć, wybierz radio_button_checked Zrzut sterty i włącz radio_button_checked Udostępnij wewnętrzne elementy (zawiera dodatkowe szczegóły dotyczące implementacji).
- Odtwórz problem, który sprawiał, że
InternalNode
zachowuje dużo pamięci. - Zrób zrzut stosu. W tym zrzucie obiekty zamiast
InternalNode
mają nazwy klas C++.
(object shape)
Jak opisano w artykule Szybkie właściwości w wersji 8, V8 śledzi klasy ukryte (lub kształty), aby można było skutecznie przedstawić wiele obiektów o tych samych właściwościach w tej samej kolejności. Ta kategoria zawiera ukryte klasy system / Map
(niezwiązane z JavaScriptem Map
) oraz powiązane dane.
(sliced string)
Gdy V8 musi przejąć podłańcuch, np. gdy kod JavaScript wywołuje String.prototype.substring()
, V8 może zdecydować się na przydzielenie obiektu wyciętego ciągu zamiast kopiowania wszystkich odpowiednich znaków z pierwotnego ciągu. Ten nowy obiekt zawiera wskaźnik do pierwotnego ciągu znaków i opisuje, jakiego zakresu znaków z pierwotnego ciągu ma użyć.
Z punktu widzenia kodu JavaScript są to zwykłe ciągi znaków, które działają jak każdy inny ciąg. Jeśli pocięty ciąg znaków zachowuje dużą ilość pamięci, program mógł wywołać problem 2869 i skuteczniej je „spłaszczyć”. przecięty ciąg znaków.
system / Context
Obiekty wewnętrzne typu system / Context
zawierają zmienne lokalne z zamknięcia – zakresu JavaScriptu, do którego ma dostęp funkcja zagnieżdżona.
Każda instancja funkcji zawiera wewnętrzny wskaźnik do funkcji Context
, która jest wykonywana, by umożliwić dostęp do tych zmiennych. Obiekty Context
nie są widoczne bezpośrednio z JavaScriptu, ale masz nad nimi bezpośrednią kontrolę.
(system)
Ta kategoria zawiera różne obiekty wewnętrzne, które nie zostały (jeszcze) przypisane do bardziej istotnej kategorii.
Porównanie
Widok Porównanie umożliwia znajdowanie obiektów, które wyciekły, przez porównywanie ze sobą kilku zrzutów. Na przykład wykonanie działania i cofnięcie go, np. otwarcie i zamknięcie dokumentu, nie powinno pozostawiać po sobie żadnych dodatkowych obiektów.
Aby sprawdzić, czy określona operacja nie powoduje wycieków:
- Przed wykonaniem operacji zrób zrzut stosu.
- Wykonaj operację. Oznacza to, że wejdź w interakcję ze stroną w taki sposób, który Twoim zdaniem może powodować wyciek.
- Wykonaj operację odwrotną. czyli wykonaj odwrotną interakcję i powtórz tę czynność kilka razy.
- Zrób drugi zrzut stosu i zmień jego widok na Porównanie, porównując go z Zrzut 1.
Widok Porównanie pokazuje różnicę między dwoma zrzutami. Podczas rozwijania sumy wyświetlane są wpisy, dodane i usunięte instancje obiektów:
Widok kontenera
Widok Ogrzewanie to „widok z lotu ptaka”. struktury obiektów aplikacji. Pozwala zajrzeć do wnętrza funkcji, obserwować wewnętrzne obiekty maszyn wirtualnych, które razem składają się na obiekty JavaScript, oraz określić, ile pamięci używa Twoja aplikacja na bardzo niskim poziomie.
Widok ten zawiera kilka punktów wejścia:
- obiekty DOMWindow. Globalne obiekty kodu JavaScript.
- Elementy główne GC. Katalogi główne GC używane przez moduł do czyszczenia pamięci maszyny wirtualnej. Elementy główne GC mogą zawierać wbudowane mapy obiektów, tabele symboli, stosy wątków maszyn wirtualnych, pamięci podręczne kompilacji, zakresy uchwytów i uchwyty globalne.
- Obiekty natywne. Obiekty przeglądarki „przekazano” wewnątrz maszyny wirtualnej JavaScript, aby umożliwić automatyzację, np. węzłów DOM i reguł CSS.
Sekcja elementów zachowujących
W sekcji Elementy składowe u dołu panelu Pamięć znajdują się obiekty wskazujące obiekt wybrany w widoku. Gdy wybierzesz inne obiekty w dowolnym widoku oprócz widoku Statystyki, panel Pamięć aktualizuje sekcję Elementy składowe.
W tym przykładzie wybrany ciąg jest przechowywany przez właściwość x
instancji Item
.
Ignoruj elementy zachowujące
Możesz ukryć elementy zachowujące, aby sprawdzić, czy inne obiekty zachowują wybrany. Dzięki tej opcji nie musisz najpierw usuwać jej z kodu, a potem jeszcze raz robić zrzutu stosu.
Aby ukryć element zachowujący, kliknij prawym przyciskiem myszy i wybierz Ignoruj tego użytkownika. Zignorowane osoby zachowujące są oznaczone jako ignored
w kolumnie Odległość. Aby nie ignorować wszystkich użytkowników, kliknij playlist_remove Przywróć zignorowane elementy zachowujące na pasku działań u góry.
Znajdowanie konkretnego obiektu
Aby znaleźć obiekt w zebranej stercie, możesz wyszukać go za pomocą Ctrl + F i wpisać identyfikator obiektu.
Nazwij funkcje, aby odróżnić zamknięcia
Warto nazwać funkcje, aby rozróżnić zamknięcia na zrzucie ekranu.
Na przykład ten kod nie korzysta z funkcji nazwanych:
function createLargeClosure() {
var largeStr = new Array(1000000).join('x');
var lC = function() { // this is NOT a named function
return largeStr;
};
return lC;
}
W tym przykładzie:
function createLargeClosure() {
var largeStr = new Array(1000000).join('x');
var lC = function lC() { // this IS a named function
return largeStr;
};
return lC;
}
Wykrywanie wycieków DOM
Program profilujący sterty może odzwierciedlać dwukierunkowe zależności między obiektami natywnymi dla przeglądarki (węzły DOM i reguły CSS) a obiektami JavaScript. Pomaga to wykryć niewidoczne wyciek danych wynikające z unoszących się wokół zapomnianych, odłączonych poddrzew DOM.
Wycieki DOM mogą być większe, niż Ci się wydaje. Przyjrzyjmy się temu przykładowi. Kiedy jest odprowadzany śmieci w: #tree
?
var select = document.querySelector;
var treeRef = select("#tree");
var leafRef = select("#leaf");
var body = select("body");
body.removeChild(treeRef);
//#tree can't be GC yet due to treeRef
treeRef = null;
//#tree can't be GC yet due to indirect
//reference from leafRef
leafRef = null;
//#NOW #tree can be garbage collected
Funkcja #leaf
zachowuje odwołanie do elementu nadrzędnego (parentNode
) i powtarza się do wartości #tree
, więc tylko
gdy wartość leafRef
ma wartość null, to całe drzewo w kolumnie #tree
jest kandydatem do GC.