Dowiedz się, jak rejestrować obrazy sterty za pomocą opcji Pamięć > Profile > Obraz sterty, i jak znajdować wycieki 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 pomocą tego narzędzia możesz robić zrzuty pamięci podręcznej JS, analizować wykresy pamięci, porównywać zrzuty i wyszukiwać wycieki pamięci. Więcej informacji znajdziesz w artykule Drzewo obiektów.
Zrób zdjęcie
Aby wykonać zrzut sterty:
- Na stronie, którą chcesz profilować, otwórz Narzędzia deweloperskie i otwórz panel Pamięć.
- Wybierz typ profilowania Zrzut stosu, a następnie wybierz wystąpienie maszyny wirtualnej JavaScript i kliknij Zrób zrzut.
Gdy panel Pamięć wczytuje i przetwarza zrzut, pod tytułem zrzutu w sekcji SNAPSHOTS pod tytułem zrzutu ekranu wyświetli się łączny rozmiar osiągalnych obiektów JavaScript.
Zrzuty ekranu zawierają tylko obiekty z grafu pamięci, które są dostępne z obiektu globalnego. Tworzenie zrzutu zawsze zaczyna się od usuwania elementów zbędących.
Wyczyść zrzuty
Aby usunąć wszystkie migawki, 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 danych w DOM. |
Porównanie | Różnice między 2 zrzutami | Użyj go do porównania co najmniej 2 migawek (przed i po wykonaniu operacji). Aby sprawdzić, czy występuje wyciek pamięci i jaka jest jego przyczyna, sprawdź różnicę w ilości zwolnionej pamięci i liczbie odwołań. |
Ograniczenie | Zawartość stosu | Zapewnia lepszy widok struktury obiektów i pomaga analizować obiekty, do których odwołuje się globalna przestrzeń nazw (okno), aby znaleźć to, co je utrzymuje. Za pomocą tego narzędzia możesz analizować zamknięcia i szczegółowo badać obiekty. |
Statystyki | Wykres kołowy alokacji pamięci | Zobacz względne rozmiary części pamięci przydzielonych do kodu, ciągów znaków, tablic JS, tablic typowych i obiektów systemowych. |
Widok podsumowania
Najpierw w widoku Podsumowanie otworzy się migawka stosu, która w kolumnie zawiera listę konstruktorów. Możesz rozwinąć konstruktory, aby zobaczyć instancje obiektów.
Aby odfiltrować nieistotne konstruktory, wpisz nazwę, którą chcesz sprawdzić, w polu Filtr klasy u góry widoku Podsumowanie.
Liczby obok nazw konstruktorów wskazują łączną liczbę obiektów utworzonych za pomocą konstruktora. Widok Podsumowanie zawiera też te kolumny:
- Odległość pokazuje odległość od węzła źródla, korzystając z najkrótszej prostej ścieżki przez węzły.
- Shallow size (płytki rozmiar) to suma rozmiarów płytkich wszystkich obiektów utworzonych przez dany 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 to maksymalny rozmiar przechowywany w ramach tego samego zbioru obiektów. Zatrzymany rozmiar to rozmiar pamięci, który możesz zwolnić, usuwając obiekt i uniemożliwiając dostęp do jego elementów zależnych. Zobacz też Rozmiary obiektów.
Gdy rozwiniesz konstruktor, w widoku Podsumowanie zobaczysz wszystkie jego wystąpienia. Każda instancja ma w odpowiednich kolumnach podział na rozmiary płytkie i zatrzymane. Liczba po znaku @
to unikalny identyfikator obiektu. Umożliwia porównywanie zrzutów stosu na podstawie 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 zarejestrowane 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 ekranu powoduje dodanie kolejnej wartości tego filtra do listy rozwijanej.
- Zduplikowane ciągi: wartości ciągu, które zostały zapisane w pamięci wielokrotnie.
- Obiekty zachowane przez odłączone węzły: obiekty, które są utrzymywane, ponieważ odłączony węzeł DOM się do nich odwołuje.
- Obiekty zachowane przez konsolę Narzędzi deweloperskich: obiekty przechowywane w pamięci, ponieważ zostały ocenione lub z nimi przeprowadzono interakcję za pomocą konsoli Narzędzi deweloperskich.
Specjalne wpisy w sekcji Podsumowanie
Oprócz grupowania według konstruktorów widok Podsumowanie grupuje obiekty również według:
- Wbudowane funkcje, takie jak
Array
lubObject
. - Elementy HTML pogrupowane według tagów, np.
<div>
,<a>
,<img>
i inne. - Funkcje zdefiniowane w kodzie.
- Kategorie specjalne, które nie są oparte na konstruktorach.
(array)
Ta kategoria obejmuje różne wewnętrzne obiekty podobne do tablic, które nie odpowiadają bezpośrednio obiektom widocznym w JavaScript.
Na przykład zawartość obiektów JavaScript Array
jest przechowywana w dodatkowym obiekcie wewnętrznym o nazwie (object elements)[]
, aby ułatwić zmianę rozmiaru. Podobnie nazwane właściwości w obiektach JavaScriptu są często przechowywane w dodatkowych wewnętrznych obiektach o nazwie (object properties)[]
, które są też wymienione w kategorii (array)
.
(compiled code)
Ta kategoria obejmuje 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 wykonywana wiele razy, V8 przeznacza na nią więcej pamięci, aby mogła działać szybciej. Jeśli funkcja nie była wykonywana przez jakiś czas, V8 może usunąć jej dane wewnętrzne.
(concatenated string)
Gdy V8 konkatenuje 2 ciągi znaków, np. za pomocą operatora JavaScript +
, może przedstawić wynik wewnętrznie jako „konkatenowany ciąg znaków”, 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 2 ciągi źródłowe. Dzięki temu V8 oszczędza czas i pamięć. Z perspektywy kodu JavaScript są to zwykłe ciągi znaków, które zachowują się jak każdy inny ciąg znaków.
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ę wyświetlania zasobów wewnętrznych w zrzutach stosu.
- Otwórz panel Pamięć, wybierz Zrzut Heap i włącz Udostępnij dane wewnętrzne (w tym dodatkowe szczegóły charakterystyczne dla implementacji).
- Odtwórz problem, który spowodował, że
InternalNode
zatrzymało dużo pamięci. - Zrób zrzut stosu. Na tym zrzucie obiekty mają nazwy klas C++, a nie
InternalNode
.
(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 reprezentować wiele obiektów o tych samych właściwościach w tym samym porządku. Ta kategoria zawiera ukryte zajęcia o nazwie system / Map
(niezwiązane z JavaScriptem Map
) oraz powiązane z nimi 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 kopiować wszystkie istotne znaki z oryginalnego 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 pokrojowy ciąg znaków zajmuje dużo pamięci, program może wywołać problem 2869 i można podjąć celowe działania, aby „spłaszczyć” pokrojowy 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 Context
, w którym jest wykonywana, dzięki czemu może uzyskiwać dostęp do tych zmiennych. Chociaż obiekty Context
nie są widoczne bezpośrednio w JavaScript, masz nad nimi bezpośrednią kontrolę.
(system)
Ta kategoria zawiera różne obiekty wewnętrzne, które nie zostały (jeszcze) sklasyfikowane w żaden bardziej znaczący sposób.
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 czynności i jej cofnięcie, np. otwarcie dokumentu i jego zamknięcie, nie powinno pozostawiać dodatkowych obiektów.
Aby sprawdzić, czy dana operacja nie powoduje wycieków:
- Przed wykonaniem operacji zrób zrzut stosu.
- Wykonywanie operacji. Oznacza to, że musisz wejść w interakcję ze stroną w sposób, który Twoim zdaniem może spowodować wyciek.
- Wykonaj odwrotną operację. Oznacza to, że należy wykonać odwrotną interakcję i powtórzyć ją kilka razy.
- Zrób drugi zrzut pamięci podręcznej i zmień jego widok na Porównanie, aby porównać go z zrzutem 1.
Widok Porównanie pokazuje różnicę między dwoma zrzutami. Po rozwinięciu rekordu sumy wyświetlane są dodane i usunięte wystąpienia obiektu:
Widok Zawieranie
Widok Zawiera to „widok z lotu ptaka” struktury obiektów aplikacji. Umożliwia ona zaglądanie do funkcji zamykających, obserwowanie wewnętrznych obiektów maszyny wirtualnej, które razem tworzą obiekty JavaScript, oraz określanie, ile pamięci zużywa aplikacja na bardzo niskim poziomie.
Widok zawiera kilka punktów wejścia:
- obiekty DOMWindow, Obiekty globalne dla kodu JavaScript.
- GC roots. korzenie GC używane przez zbieracz śmieci 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 „przesyłane” do maszyny wirtualnej JavaScriptu, aby umożliwić automatyzację, np. węzły DOM i reguły CSS.
Sekcja Aparaty retencyjne
Sekcja Zatrzymacze u dołu panelu Pamięć zawiera obiekty, które wskazują obiekt wybrany w widoku. Gdy wybierzesz inne obiekty w dowolnym widoku (z wyjątkiem widoku Statystyki), panel Pamięć zaktualizuje sekcję Utrzymujący.
W tym przykładzie wybrany ciąg znaków jest przechowywany przez właściwość x
instancji Item
.
Ignoruj elementy zachowujące
Możesz ukryć elementy stałe, aby sprawdzić, czy inne obiekty nie zastępują wybranego. Dzięki tej opcji nie musisz najpierw usuwać jej z kodu, a potem jeszcze raz robić zrzutu stosu.
Aby ukryć załącznik, kliknij prawym przyciskiem myszy i wybierz Ignoruj ten załącznik. Zignorowane osoby zachowujące są oznaczone jako ignored
w kolumnie Odległość. Aby przestać ignorować wszystkie rezerwacje, na górze na pasku działań kliknij Przywróć zignorowane rezerwacje.
Znajdowanie konkretnego obiektu
Aby znaleźć obiekt w zbiorzonej stercie, możesz użyć skrótu 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 w tym kodzie nie używa się funkcji nazwanych:
function createLargeClosure() {
var largeStr = new Array(1000000).join('x');
var lC = function() { // this is NOT a named function
return largeStr;
};
return lC;
}
Ten przykład:
function createLargeClosure() {
var largeStr = new Array(1000000).join('x');
var lC = function lC() { // this IS a named function
return largeStr;
};
return lC;
}
Odkrywanie wycieków danych do DOM
Profilator sterty może odzwierciedlać dwukierunkowe zależności między obiektami natywnymi przeglądarki (węzłami DOM i regułami 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. Rozważ ten przykład. Kiedy odbierane są odpady #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.