Bagian ini menjelaskan istilah umum yang digunakan dalam analisis memori, dan berlaku untuk berbagai alat pembuatan profil memori untuk berbagai bahasa.
Istilah dan konsep yang dijelaskan di sini merujuk pada Profiler Heap Chrome DevTools. Jika Anda pernah menggunakan Java, .NET, atau beberapa profiler memori lainnya, ini mungkin merupakan pengingat.
Ukuran objek
Anggap memori sebagai grafik dengan jenis primitif (seperti angka dan string) dan objek (array asosiatif). Secara visual, hal ini dapat direpresentasikan sebagai grafik dengan sejumlah titik yang saling terhubung sebagai berikut:
Objek dapat menyimpan memori dengan dua cara:
- Secara langsung oleh objek itu sendiri.
- Secara implisit dengan menyimpan referensi ke objek lain, sehingga mencegah objek tersebut dihapus secara otomatis oleh pengumpulan sampah (GC).
Saat menggunakan Heap Profiler di DevTools (alat untuk menyelidiki masalah memori yang ditemukan di panel Memory), Anda mungkin akan melihat beberapa kolom informasi yang berbeda. Dua yang paling menarik adalah Shallow Size dan Retained Size, tetapi apa yang diwakilinya?
Ukuran dangkal
Ini adalah ukuran memori yang disimpan oleh objek itu sendiri.
Objek JavaScript standar memiliki beberapa memori yang dicadangkan untuk deskripsinya dan untuk menyimpan nilai langsung. Biasanya, hanya array dan string yang dapat memiliki ukuran dangkal yang signifikan. Namun, string dan array eksternal sering kali memiliki penyimpanan utamanya di memori perender, yang hanya mengekspos objek wrapper kecil di heap JavaScript.
Memori perender adalah semua memori proses tempat halaman yang diperiksa dirender: memori native + memori heap JS halaman + memori heap JS dari semua pekerja khusus yang dimulai oleh halaman. Namun, objek kecil sekalipun dapat menyimpan memori dalam jumlah besar secara tidak langsung, dengan mencegah objek lain dibuang oleh proses pembersihan sampah memori otomatis.
Ukuran yang dipertahankan
Ini adalah ukuran memori yang dibebaskan setelah objek itu sendiri dihapus beserta objek dependennya yang dibuat tidak dapat dijangkau dari root GC.
Akar GC terdiri dari handle yang dibuat (lokal atau global) saat membuat referensi dari kode native ke objek JavaScript di luar V8. Semua handle tersebut dapat ditemukan dalam snapshot heap di bagian Akar GC > Cakupan handle dan Akar GC > Handle global. Menjelaskan handle dalam dokumentasi ini tanpa membahas detail penerapan browser mungkin membingungkan. Root GC dan handle bukanlah sesuatu yang perlu Anda khawatirkan.
Ada banyak root GC internal yang sebagian besar tidak menarik bagi pengguna. Dari sudut pandang aplikasi, ada jenis root berikut:
- Objek global jendela (di setiap iframe). Ada kolom jarak dalam snapshot heap yang merupakan jumlah referensi properti pada jalur retensi terpendek dari jendela.
- Hierarki DOM dokumen yang terdiri dari semua node DOM native yang dapat dijangkau dengan menjelajahi dokumen. Tidak semua darinya mungkin memiliki wrapper JS, tetapi jika memilikinya, wrapper akan aktif saat dokumen aktif.
- Terkadang objek dapat dipertahankan oleh konteks debugger dan konsol DevTools (misalnya, setelah penilaian konsol). Buat snapshot heap dengan konsol yang jelas dan tidak ada titik henti sementara yang aktif di debugger.
Grafik memori dimulai dengan root, yang mungkin berupa objek window
browser atau objek Global
modul Node.js. Anda tidak mengontrol cara objek root ini dihapus oleh GC.
Apa pun yang tidak dapat dijangkau dari root akan mendapatkan GC.
Objek yang mempertahankan hierarki
Heap adalah jaringan objek yang saling terhubung. Dalam dunia matematika, struktur ini disebut grafik atau grafik memori. Grafik dibuat dari node yang terhubung melalui edge, yang keduanya diberi label.
- Node (atau objek) diberi label menggunakan nama fungsi konstruktor yang digunakan untuk mem-build-nya.
- Edge diberi label menggunakan nama properti.
Pelajari cara merekam profil menggunakan Heap Profiler. Beberapa hal menarik yang dapat kita lihat dalam rekaman Heap Profiler berikut mencakup jarak: jarak dari root GC. Jika hampir semua objek dengan jenis yang sama berada pada jarak yang sama, dan beberapa objek berada pada jarak yang lebih besar, hal ini patut diselidiki.
Dominator
Objek Dominator terdiri dari struktur hierarki karena setiap objek memiliki tepat satu dominator. Penguasa objek mungkin tidak memiliki referensi langsung ke objek yang dikuasainya; yaitu, pohon penguasa bukan merupakan spanning tree grafik.
Dalam diagram berikut:
- Node 1 mendominasi node 2
- Node 2 mendominasi node 3, 4, dan 6
- Node 3 mendominasi node 5
- Node 5 mendominasi node 8
- Node 6 mendominasi node 7
Pada contoh berikut, node #3
adalah pengontrol #10
, tetapi #7
juga ada di setiap jalur sederhana
dari GC ke #10
. Oleh karena itu, objek B adalah pengontrol objek A jika B ada di setiap jalur sederhana
dari root ke objek A.
Spesifikasi V8
Saat membuat profil memori, sebaiknya pahami mengapa snapshot heap terlihat dengan cara tertentu. Bagian ini menjelaskan beberapa topik terkait memori yang secara khusus sesuai dengan virtual machine JavaScript V8 (VM V8 atau VM).
Representasi objek JavaScript
Ada tiga jenis primitif:
- Angka (misalnya, 3.14159..)
- Boolean (benar atau salah)
- String (misalnya, 'Werner Heisenberg')
Nilai ini tidak dapat mereferensikan nilai lain dan selalu berupa node daun atau node akhir.
Angka dapat disimpan sebagai:
- nilai bilangan bulat 31-bit langsung yang disebut bilangan bulat kecil (SMI), atau
- objek heap, yang disebut sebagai angka heap. Angka heap digunakan untuk menyimpan nilai yang tidak sesuai dengan formulir SMI, seperti ganda, atau saat nilai perlu disekotak, seperti menetapkan properti di dalamnya.
String dapat disimpan di:
- heap VM, atau
- secara eksternal di memori perender. Objek wrapper dibuat dan digunakan untuk mengakses penyimpanan eksternal, misalnya, sumber skrip dan konten lain yang diterima dari Web disimpan, bukan disalin ke heap VM.
Memori untuk objek JavaScript baru dialokasikan dari heap JavaScript khusus (atau heap VM). Objek ini dikelola oleh pengumpulan sampah V8 dan oleh karena itu, akan tetap aktif selama ada setidaknya satu referensi kuat ke objek tersebut.
Objek native adalah semua hal yang tidak ada di heap JavaScript. Objek native, berbeda dengan objek heap, tidak dikelola oleh pengumpulan sampah V8 selama masa aktifnya, dan hanya dapat diakses dari JavaScript menggunakan objek wrapper JavaScript-nya.
String kons adalah objek yang terdiri dari pasangan string yang disimpan, lalu digabungkan, dan merupakan hasil konkatensi. Penggabungan konten string cons hanya terjadi sesuai kebutuhan. Contohnya adalah saat substring string gabungan perlu dibuat.
Misalnya, jika menyambungkan a dan b, Anda akan mendapatkan string (a, b) yang mewakili hasil penggabungan. Jika kemudian Anda menyambungkan d dengan hasil tersebut, Anda akan mendapatkan string cons lain ((a, b), d).
Array - Array adalah Objek dengan kunci numerik. Keduanya digunakan secara ekstensif di VM V8 untuk menyimpan data dalam jumlah besar. Kumpulan key-value pair yang digunakan seperti kamus dicadangkan oleh array.
Objek JavaScript standar dapat berupa salah satu dari dua jenis array yang digunakan untuk menyimpan:
- properti bernama, dan
- elemen numerik
Jika jumlah properti sangat sedikit, properti tersebut dapat disimpan secara internal di objek JavaScript itu sendiri.
Peta-objek yang mendeskripsikan jenis objek dan tata letaknya. Misalnya, peta digunakan untuk menjelaskan hierarki objek implisit untuk akses properti yang cepat.
Grup objek
Setiap grup objek native terdiri dari objek yang memiliki referensi bersama satu sama lain. Misalnya, sub-pohon DOM tempat setiap node memiliki link ke induknya dan link ke turunan berikutnya dan saudara berikutnya, sehingga membentuk grafik yang terhubung. Perhatikan bahwa objek native tidak direpresentasikan dalam heap JavaScript—itulah sebabnya objek tersebut memiliki ukuran nol. Sebagai gantinya, objek wrapper akan dibuat.
Setiap objek wrapper menyimpan referensi ke objek native yang sesuai, untuk mengalihkan perintah ke objek tersebut. Pada gilirannya, grup objek menyimpan objek wrapper. Namun, hal ini tidak akan membuat siklus yang tidak dapat dikumpulkan, karena GC cukup cerdas untuk merilis grup objek yang wrapper-nya tidak lagi direferensikan. Namun, lupa merilis satu wrapper akan menahan seluruh grup dan wrapper terkait.