Terminologia związana z pamięcią

Meggin Kearney
Meggin Kearney

W tej sekcji opisano typowe terminy używane w analizie pamięci i ma zastosowanie do różnych narzędzi do profilowania pamięci dla różnych języków.

Opisane tu terminy i definicje odnoszą się do narzędzia do profilowania sterty w Narzędziach deweloperskich w Chrome. Jeśli zdarzyło Ci się korzystać z programu Java, .NET lub innego programu do profilowania pamięci, warto odświeżyć sobie tę wiedzę.

Rozmiary obiektów

Pamięć to wykres z typami podstawowymi (takimi jak liczby i ciągi znaków) oraz obiektami (tablicami asocjacyjnymi). Może on mieć postać wykresu z liczbą połączonych ze sobą punktów:

Wizualna reprezentacja pamięci

Obiekt może przechowywać pamięć na 2 sposoby:

  • Bezpośrednio przez obiekt.
  • pośrednio przez przechowywanie odwołań do innych obiektów, zapobiegając ich automatycznemu usuwaniu przez funkcję czyszczenia pamięci (GC).

Podczas pracy z narzędziem profilowania sterty w Narzędziach deweloperskich (czyli narzędziu do badania problemów z pamięcią, które znajdziesz w sekcji „Profiles”), prawdopodobnie będziesz widzieć kilka różnych kolumn z informacjami. Dwa z nich to płytki rozmiar i przechowywany rozmiar, ale co one oznaczają?

Płytki i zachowany rozmiar

Płytki rozmiar

Jest to rozmiar pamięci, który jest przechowywany przez sam obiekt.

Typowe obiekty JavaScript mają część pamięci zarezerwowaną na opis i przechowywanie wartości bezpośrednich. Zazwyczaj tylko tablice i ciągi mogą mieć płytki rozmiar. Ciągi tekstowe i tablice zewnętrzne często mają jednak swoją główną pamięć w pamięci mechanizmu renderowania, przez co wyświetla się tylko niewielki obiekt opakowany na stercie JavaScriptu.

Pamięć mechanizmu renderowania to cała pamięć procesu, w którym renderowana jest sprawdzana strona: pamięć natywna + sterta pamięci JS strony + pamięć sterty JS wszystkich dedykowanych instancji roboczych uruchomionych przez stronę. Mimo to nawet mały obiekt może pośrednio przechowywać dużą ilość pamięci, zapobiegając wyrzucaniu innych obiektów przez proces automatycznego czyszczenia pamięci.

Zachowany rozmiar

Jest to rozmiar pamięci, który jest zwalniany po usunięciu obiektu wraz z jego zależnymi obiektami, które stały się nieosiągalne z głównych obiektów GC.

Główne elementy interfejsu GC składają się z nicków, które są tworzone (lokalne lub globalne) przy tworzeniu odwołania z kodu natywnego do obiektu JavaScript poza wersją 8. Wszystkie takie uchwyty można znaleźć w zrzutie sterty w sekcji Główne nicki GC > Obsługa zakresu i Główne nicki GC > uchwyty globalne. Opisywanie uchwytów w tej dokumentacji bez zagłębiania się w szczegóły implementacji przeglądarki może być mylące. Nie musisz się przejmować ani poziomami głównymi GC, ani nickami.

Istnieje wiele wewnętrznych katalogów głównych GC, z których większość nie jest interesująca dla użytkowników. Z punktu widzenia aplikacji można wyróżnić następujące rodzaje rdzeni:

  • Obiekt globalny okna (w każdym elemencie iframe). W zrzutach sterty znajduje się pole odległości, które podaje liczbę odwołań do właściwości na najkrótszej ścieżce przechowywania z okna.
  • Drzewo DOM dokumentu zawierające wszystkie natywne węzły DOM, które są osiągalne podczas przemierzania dokumentu. Nie wszystkie z nich mają otoki JS, ale jeśli będą miały, będą aktywne do końca życia dokumentu.
  • Czasami obiekty mogą być przechowywane przez kontekst debugera i konsolę Narzędzi deweloperskich (np. po ocenie konsoli). Twórz zrzuty stosu z przejrzystą konsolą i bez aktywnych punktów przerwania w debugerze.

Wykres pamięci zaczyna się od poziomu głównego, który może być obiektem window przeglądarki lub obiektem Global modułu Node.js. Nie masz kontroli nad tym, w jaki sposób ten obiekt główny jest tworzony przez GC.

Nie można sterować obiektem głównym

GC będzie dotyczyć tego, co nie jest osiągalne z poziomu głównego.

Drzewo przechowywania obiektów

Stos to sieć połączonych ze sobą obiektów. W świecie matematycznym struktura ta nosi nazwę wykresu, czyli grafu pamięci. Wykres jest konstruowany z węzłów połączonych krawędziami, które są oznaczone etykietami.

  • Węzły (lub obiekty) są oznaczone etykietą funkcji konstruktora, która została użyta do ich utworzenia.
  • Brzegowy są oznaczane etykietami za pomocą nazw właściwości.

Dowiedz się, jak zarejestrować profil za pomocą narzędzia do profilowania sterty. Niektóre przyciągające wzrok coś, co pokazujemy na poniższym nagraniu Heap Profiler, to między innymi odległość: odległość od rdzenia GC. Jeśli prawie wszystkie obiekty tego samego typu znajdują się w tej samej odległości, a kilka z nich jest większa, warto to zbadać.

Odległość od pierwiastka

Dominatorzy

Obiekty dominujące są zbudowane z struktury drzewa, ponieważ każdy z nich ma dokładnie jeden dominator. Dominatorem obiektu może brakować bezpośrednich odwołań do obiektu, który dominuje. Drzewo dominatora nie jest drzewem rozpinającym grafu.

Na tym schemacie:

  • Węzeł 1 dominuje w węźle 2
  • Węzeł 2 dominuje w węzłach 3, 4 i 6
  • Węzeł 3 dominuje w węźle 5
  • Węzeł 5 dominuje w węźle 8
  • Węzeł 6 dominuje w węźle 7

Struktura drzewa dominującego

W poniższym przykładzie węzeł #3 jest dominatorem węzła #10, ale #7 występuje też w każdej prostej ścieżce od GC do #10. Obiekt B jest więc dominatorem obiektu A, jeśli znajduje się w każdej prostej ścieżce prowadzącej od katalogu głównego do obiektu A.

Ilustracja animowanego dominatora

Szczegóły dotyczące V8

Podczas profilowania pamięci warto zrozumieć, dlaczego zrzuty stosu wyglądają w określony sposób. W tej sekcji omawiamy niektóre tematy związane z pamięcią, które dotyczą maszyny wirtualnej V8 JavaScript (maszyny wirtualnej V8 lub maszyny wirtualnej w wersji 8).

Reprezentacja obiektu JavaScript

Istnieją 3 typy podstawowe:

  • Liczby (np. 3,14159...)
  • Wartość logiczna (true lub false)
  • Ciągi tekstowe (np. „Werner Heisenberg”)

Nie mogą odwoływać się do innych wartości i są zawsze liśćmi lub węzłami zamykającymi.

Numery mogą być przechowywane jako:

  • natychmiastowe 31-bitowe wartości całkowite nazywane małymi liczbami całkowitymi (SMI) lub
  • obiektach sterty nazywanych numerami sterty. Numery sterty służą do przechowywania wartości, które nie pasują do formularza SMI, takich jak double, lub gdy wartość trzeba obramować, np. można ustawić jej właściwości.

Ciągi tekstowe mogą być przechowywane w jednym z tych miejsc:

  • stertę maszyn wirtualnych lub
  • zewnętrznie w pamięci mechanizmu renderowania. Tworzony jest obiekt opakowujący, który pozwala uzyskać dostęp do pamięci zewnętrznej. Na przykład źródła skryptów i inne treści odebrane z internetu są przechowywane, a nie kopiowane na stertę maszyn wirtualnych.

Pamięć dla nowych obiektów JavaScript jest przydzielana z dedykowanej sterty JavaScript (lub sterty maszyn wirtualnych). Obiekty te są zarządzane przez funkcję czyszczenia pamięci V8 i dlatego pozostaną aktywne, dopóki będzie do nich co najmniej jedno silne odniesienie.

Obiekty natywne to wszystkie pozostałe elementy, których nie ma na stercie JavaScriptu. Obiekt natywny, w przeciwieństwie do obiektu stosu, nie jest zarządzany przez funkcję czyszczenia pamięci V8 przez cały okres użytkowania i można uzyskać do niego dostęp wyłącznie z poziomu JavaScriptu za pomocą obiektu otoki JavaScriptu.

Ciąg znaków wad to obiekt składający się z par ciągów zapisanych, a następnie złączonych, będący wynikiem konkatenacji. Złączanie treści ciągu znaków odbywa się tylko wtedy, gdy jest to konieczne. Można to na przykład zrobić, gdy trzeba utworzyć podłańcuch połączonego ciągu znaków.

Jeśli np. połączysz a i b, otrzymasz ciąg znaków (a i b), który reprezentuje wynik połączenia. Jeśli później połączysz z tym wynikiem znak d, otrzymasz kolejny ciąg znaków z wartościami wad (a, b), d.

Tablice – tablica to obiekt z kluczami liczbowymi. Są one intensywnie używane w maszynie wirtualnej V8 do przechowywania dużych ilości danych. Tablice tworzą kopie zapasowe zbiorów par klucz-wartość używanych np. w słownikach.

Typowym obiektem JavaScriptu może być jeden z 2 typów tablic służących do przechowywania:

  • właściwości nazwane oraz
  • elementy numeryczne

Jeśli jest bardzo mało właściwości, można je przechowywać wewnętrznie w samym obiekcie JavaScriptu.

Mapuj – obiekt opisujący rodzaj obiektu i jego układ. Na przykład mapy służą do opisywania niejawnych hierarchii obiektów w celu szybkiego dostępu do właściwości.

Grupy obiektów

Każda grupa obiektów natywnych składa się z obiektów, które mają do siebie nawzajem odwołania. Rozważmy na przykład drzewo podrzędne DOM, w którym każdy węzeł ma link do swojego elementu nadrzędnego oraz link do kolejnego elementu podrzędnego i kolejnego elementu równorzędnego, tworząc w ten sposób połączony wykres. Obiekty natywne nie są reprezentowane na stercie JavaScriptu – dlatego mają zerowy rozmiar. Zamiast tego tworzone są obiekty kodu.

Każdy obiekt otoki zawiera odwołanie do odpowiedniego obiektu natywnego na potrzeby przekierowania do niego poleceń. W zamian grupa obiektów zawiera obiekty otoki. Nie powoduje to jednak cyklu niewystarczającego do pobrania, ponieważ GC jest na tyle inteligentne, by zwolnić grupy obiektów, których kody nie są już odnotowywane. Jeśli jednak zapomnisz o wydaniu jednego kodu, całą grupę i powiązane z nią elementy otaczające.