Dowiedz się, jak zapisywać zrzuty stosu za pomocą opcji Pamięć > Profile > Zrzut stosu i znajdowanie wycieków pamięci.
Profilator sterty pokazuje dystrybucję pamięci między obiekty JavaScript Twojej strony i powiązane węzły 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 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 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
Wyczyść wszystkie profile:Wyświetl zrzuty
Aby sprawdzać migawki z różnych perspektyw i do różnych celów, wybierz jeden z widoków w 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 2 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 rozwinąć konstruktory, aby zobaczyć instancje obiektów.
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. Zwykle tablice i ciągi mają większy rozmiar płytki. 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. Ustaw domyślnie.
- 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 migawkami 1 i 2: wyświetl różnicę w liczbie obiektów między najnowszym a poprzednim migawką. Każdy nowy zrzut dodaje do listy przyrost wartości tego filtra.
- Zduplikowane ciągi: wartości ciągu, które zostały zapisane w pamięci wielokrotnie.
- 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.
Specjalne wpisy w sekcji Podsumowanie
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 konkatenuje 2 łańcuchy, np. za pomocą operatora JavaScript +
, może wewnętrznie reprezentować wynik jako „łańcuch konkatenowany”, czyli 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. Dzięki temu V8 oszczędza 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 > Eksperymenty > Pokaż opcję udostępniania zasobów wewnętrznych w zrzutach sterty.
- Otwórz panel Pamięć, wybierz migawka stosu i włącz Udostępnij dane wewnętrzne (w tym dodatkowe szczegóły charakterystyczne dla 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 Fast Properties in V8 (w języku angielskim), V8 śledzi ukryte klasy (lub kształty), aby można było efektywnie przedstawiać wiele obiektów o tych samych właściwościach w tym samym porządku. 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 oryginalnego ciągu znaków i opisuje, który zakres znaków z pierwotnego ciągu ma być użyty.
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 closure, czyli zakresu JavaScriptu, do którego może mieć 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. Po rozwinięciu pozycji sumy wyświetlane są dodane i usunięte instancje obiektu:
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.
- GC roots. Katalogi główne GC używane przez moduł do czyszczenia pamięci maszyny wirtualnej. Korzenie GC mogą składać się z wbudowanych map obiektów, tabel symboli, stosów wątków maszyny wirtualnej, pamięci podręcznej kompilacji, zakresów uchwytów i uchwytów globalnych.
- obiekty natywne, Obiekty przeglądarki „przekazano” wewnątrz maszyny wirtualnej JavaScript, aby umożliwić automatyzację, np. węzłów DOM i reguł CSS.
Sekcja Aparaty retencyjne
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
.
Ignorowanie elementów zachowujących
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 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
#leaf
przechowuje odwołanie do swojego rodzica (parentNode
) i rekurencyjnie do #tree
, więc dopiero gdy leafRef
zostanie unieważniony, cały drzewo pod #tree
staje się kandydatem do GC.