Rumah > Artikel > hujung hadapan web > Pemahaman mendalam tentang pengurusan memori JavaScript dan algoritma GC
Artikel ini membawakan anda pengetahuan yang berkaitan tentang javascript terutamanya memperkenalkan pemahaman mendalam tentang pengurusan memori JavaScript dan algoritma GC Ia terutamanya menerangkan mekanisme pengumpulan sampah JavaScript dan juga algoritma pengumpulan sampah Artikel ini menerangkan pengurusan memori dalam enjin V8 Saya harap ia akan membantu semua orang.
[Cadangan berkaitan: tutorial video javascript, bahagian hadapan web]
Apabila JavaScript mencipta pembolehubah (tatasusunan, rentetan, objek, dsb.), ia memperuntukkan memori secara automatik dan akan "secara automatik" mengeluarkan kandungan yang diperuntukkan apabila bahasa JavaScript tidak digunakan; bahasa asas lain Bahasa adalah sama, seperti bahasa C Mereka menyediakan antara muka pengurusan memori, seperti malloc()
untuk memperuntukkan ruang memori yang diperlukan dan free()
untuk melepaskan ruang memori yang diperuntukkan sebelum ini.
Kami memanggil proses melepaskan pengumpulan sampah memori Bahasa peringkat tinggi seperti JavaScript menyediakan memori peruntukan automatik dan automatik kitar semula. Kerana ini secara automatik menyebabkan ramai pembangun tidak mengambil berat tentang pengurusan memori.
Walaupun bahasa peringkat tinggi menyediakan pengurusan memori automatik, kita masih perlu mempunyai pemahaman asas tentang pengurusan dalaman Kadangkala pengurusan memori automatik mempunyai masalah, kita boleh menyelesaikannya dengan lebih baik, atau menggunakan Cara paling murah untuk menyelesaikannya.
Malah, tidak kira bahasa apa pun, kitaran pengisytiharan ingatan dibahagikan secara kasar kepada peringkat berikut:
Di bawah ini kami menerangkan setiap langkah secara terperinci:
Untuk melindungi rambut pembangun, peruntukan Memori JavaScript adalah dilengkapkan secara automatik apabila mentakrifkan pembolehubah. Kod sampel adalah seperti berikut:
let num = 123 // 给数值变量分配内存 let str = '一碗周' // 给字符串分配内存 let obj = { name: '一碗周', age: 18, } // 给对象及其包含的值分配内存 // 给数组及其包含的值分配内存(类似于对象) let arr = [1, null, 'abc'] function fun(a) { return a + 2 } // 给函数(可调用的对象)分配内存 // 函数表达式也能分配一个对象 Element.addEventListener( 'click', event => { console.log(event) }, false, )
Kadangkala memori tidak akan diperuntukkan semula, seperti kod berikut:
// 给数组及其包含的值分配内存(类似于对象) let arr = [1, null, 'abc'] let arr2 = [arr[0], arr[2]] // 这里并不会重新对分配内存,而是直接存储原来的那份内存
Proses menggunakan nilai dalam JavaScript sebenarnya adalah operasi membaca dan menulis memori yang diperuntukkan. Pembacaan dan penulisan di sini mungkin menulis pembolehubah, membaca nilai pembolehubah, menulis nilai atribut objek, dan menghantar parameter kepada fungsi, dsb.
Keluaran memori dalam JavaScript adalah automatik Masa keluaran adalah apabila nilai tertentu (alamat memori) tidak lagi digunakan dan JavaScript akan mengeluarkan memori yang didudukinya secara automatik. .
Malah, kebanyakan masalah pengurusan ingatan berlaku pada peringkat ini. Tugas paling sukar di sini ialah mencari pembolehubah yang tidak diperlukan.
Walaupun bahasa peringkat tinggi kini mempunyai mekanisme pengumpulan sampah mereka sendiri, walaupun terdapat banyak algoritma kutipan sampah sekarang, mereka tidak boleh mengitar semula semua situasi yang melampau Ini sebabnya kita perlu mempelajari pengurusan memori dan pengumpulan sampah algoritma sebab.
Seterusnya, mari kita bincangkan pengumpulan sampah dalam JavaScript dan algoritma kutipan sampah yang biasa digunakan.
Seperti yang kami katakan sebelum ini, pengurusan memori dalam JavaScript adalah automatik Memori diperuntukkan secara automatik apabila objek dibuat apabila objek tidak lagi dirujuk oleh atau tidak boleh diakses daripada akar, ia akan dikitar semula sebagai sampah.
Objek boleh dicapai dalam JavaScript hanyalah objek yang boleh diakses oleh, sama ada melalui rujukan atau rantai skop, selagi ia boleh diakses, ia dipanggil boleh dicapai objek. Terdapat piawai untuk kebolehcapaian objek boleh dicapai, iaitu, sama ada ia boleh didapati bermula dari akar di sini boleh difahami sebagai objek pembolehubah global dalam JavaScript, iaitu dalam persekitaran penyemak imbas dan window
dalam persekitaran Nod global
.
Untuk lebih memahami konsep rujukan, lihat kod berikut:
let person = { name: '一碗周', } let man = person person = null
Rajah adalah seperti berikut:
Mengikut gambar di atas, kita dapat lihat akhirnya { name: '一碗周' }
ini tidak akan dikumpul sebagai sampah kerana masih ada rujukan.
Sekarang mari kita fahami objek yang boleh dicapai Kodnya adalah seperti berikut:
function groupObj(obj1, obj2) { obj1.next = obj2 obj2.prev = obj1 return { obj1, obj2, } } let obj = groupObj({ name: '大明' }, { name: '小明' })
调用groupObj()
函数的的结果obj
是一个包含两个对象的一个对象,其中obj.obj1
的next
属性指向obj.obj2
;而obj.obj2
的prev
属性又指向obj.obj2
。最终形成了一个无限套娃。
如下图:
现在来看下面这段代码:
delete obj.obj1 delete obj.obj2.prev
我们删除obj
对象中的obj1
对象的引用和obj.obj2
中的prev
属性对obj1
的引用。
图解如下:
此时的obj1
就被当做垃圾给回收了。
GC是Garbage collection的简写,也就是垃圾回收。当GC进行工作的时候,它可以找到内存中的垃圾、并释放和回收空间,回收之后方便我们后续的进行使用。
在GC中的垃圾包括程序中不在需要使用的对象以及程序中不能再访问到的对象都会被当做垃圾。
GC算法就是工作时查找和回收所遵循的规则,常见的GC算法有如下几种:
引用计数算法的核心思想就是设置一个引用计数器,判断当前引用数是否为0 ,从而决定当前对象是不是一个垃圾,从而垃圾回收机制开始工作,释放这块内存。
引用计数算法的核心就是引用计数器 ,由于引用计数器的存在,也就导致该算法与其他GC算法有所差别。
引用计数器的改变是在引用关系发生改变时就会发生变化,当引用计数器变为0的时候,该对象就会被当做垃圾回收。
现在我们通过一段代码来看一下:
// { name: '一碗周' } 的引用计数器 + 1 let person = { name: '一碗周', } // 又增加了一个引用,引用计数器 + 1 let man = person // 取消一个引用,引用计数器 - 1 person = null // 取消一个引用,引用计数器 - 1。此时 { name: '一碗周' } 的内存就会被当做垃圾回收 man = null
引用计数算法的优点如下:
缺点如下:
就比如下面这段代码:
function fun() { const obj1 = {} const obj2 = {} obj1.next = obj2 obj2.prev = obj1 return '一碗周' } fun()
上面的代码中,当函数执行完成之后函数体的内容已经是没有作用的了,但是由于obj1
和obj2
都存在不止1个引用,导致两种都无法被回收,就造成了空间内存的浪费。
标记清除算法解决了引用计数算法的⼀些问题, 并且实现较为简单, 在V8引擎中会有被⼤量的使⽤到。
在使⽤标记清除算法时,未引用对象并不会被立即回收.取⽽代之的做法是,垃圾对象将⼀直累计到内存耗尽为⽌.当内存耗尽时,程序将会被挂起,垃圾回收开始执行.当所有的未引用对象被清理完毕 时,程序才会继续执行.该算法的核心思想就是将整个垃圾回收操作分为标记和清除两个阶段完成。
第一个阶段就是遍历所有对象,标记所有的可达对象;第二个阶段就是遍历所有对象清除没有标记的对象,同时会抹掉所有已经标记的对象,便于下次的工作。
为了区分可用对象与垃圾对象,我们需要在每⼀个对象中记录对象的状态。 因此, 我们在每⼀个对象中加⼊了⼀个特殊的布尔类型的域, 叫做marked
。 默认情况下, 对象被创建时处于未标记状态。 所以, marked
域被初始化为false
。
进行垃圾回收完毕之后,将回收的内存放在空闲链表中方便我们后续使用。
标记清除算法最大的优点就是解决了引用计数算法无法回收循环引用的对象的问题 。就比如下面这段代码:
function fun() { const obj1 = {}, obj2 = {}, obj3 = {}, obj4 = {}, obj5 = {}, obj6 = {} obj1.next = obj2 obj2.next = obj3 obj2.prev = obj6 obj4.next = obj6 obj4.prev = obj1 obj5.next = obj4 obj5.prev = obj6 return obj1 } const obj = fun()
当函数执行完毕后obj4
的引用并不是0,但是使用引用计数算法并不能将其作为垃圾回收掉,而使用标记清除算法就解决了这个问题。
Algoritma tanda dan jelas juga mempunyai kekurangan Algoritma ini akan membawa kepada pemecahan memori dan alamat terputus Selain itu, walaupun tanda dan algoritma jelas digunakan untuk mencari objek sampah, ia tidak boleh dikosongkan dengan serta-merta. dilakukan untuk kali kedua.
Algoritma penandaan dan pengisihan boleh dianggap sebagai versi dipertingkatkan bagi algoritma tanda dan jelas, dan langkahnya juga dibahagikan kepada dua peringkat: menanda dan mengosongkan.
Walau bagaimanapun, fasa pembersihan algoritma pengisihan tanda akan terlebih dahulu mengisih, menggerakkan kedudukan objek, dan akhirnya mengosongkan.
Langkah-langkahnya adalah seperti berikut:
V8 ialah enjin pelaksanaan JavaScript arus perdana Node.js dan kebanyakan penyemak imbas kini menggunakan V8 sebagai enjin JavaScript. Fungsi kompilasi V8 menggunakan kompilasi tepat dalam masa, juga dikenali sebagai terjemahan dinamik atau kompilasi masa jalan, yang merupakan kaedah melaksanakan kod komputer yang melibatkan kompilasi semasa pelaksanaan program (semasa pelaksanaan) dan bukannya sebelum pelaksanaan .
Enjin V8 mempunyai had atas memori Had atas ialah 1.5G di bawah sistem pengendalian 64-bit, dan had atas ialah 800 MB di bawah sistem pengendalian 32-bit. Mengenai mengapa had atas memori ditetapkan, sebab utama ialah enjin kandungan V8 terutamanya disediakan untuk penyemak imbas dan tidak sesuai untuk ruang yang besar ialah pengumpulan sampah bersaiz ini sangat pantas, dan pengguna mempunyai hampir tiada perasaan, dengan itu meningkatkan pengalaman pengguna.
Enjin V8 mengamalkan idea kitar semula generasi, yang membahagikan ingatan kita kepada dua kategori mengikut peraturan tertentu, satu ialah kawasan simpanan generasi baharu, dan yang lain ialah Ia adalah kawasan simpanan generasi lama.
Objek dalam generasi baharu ialah objek dengan masa kelangsungan hidup yang singkat, ia adalah objek yang baru dijana biasanya hanya menyokong kapasiti tertentu (32 MB untuk sistem pengendalian 64-bit dan 16 MB untuk 32-. Sistem operasi bit). Objek dalam generasi lama adalah objek yang mempunyai peristiwa kelangsungan hidup yang lama atau bermastautin dalam ingatan Ringkasnya, ia adalah objek yang telah bertahan selepas pengumpulan sampah generasi baru, dan kapasitinya biasanya agak besar.
Gambar berikut menunjukkan memori dalam V8:
Enjin V8 akan menggunakan GC yang berbeza mengikut objek yang berbeza . Algoritma, algoritma GC yang biasa digunakan dalam V8 adalah seperti berikut:
Seperti yang kami perkenalkan di atas, generasi baharu menyimpan objek dengan masa kemandirian yang lebih singkat. Proses kitar semula objek generasi baharu menggunakan algoritma salinan dan algoritma pengisihan tanda.
Algoritma penyalinan membahagikan kawasan memori generasi baharu kami kepada dua ruang dengan saiz yang sama Kami memanggil ruang keadaan yang sedang digunakan sebagai keadaan Dari, dan ruang keadaan ruang dipanggil To state,
Seperti yang ditunjukkan dalam rajah di bawah:
Kami menyimpan semua objek aktif dalam ruang Dari Apabila ruang hampir penuh, ia akan mencetuskan Kutipan sampah. Pertama, objek aktif dalam generasi baharu Dari angkasa perlu ditanda dan disusun Selepas penandaan selesai, objek aktif yang ditanda akan disalin ke ruang Kepada dan objek yang tidak ditanda akan dikitar semula , yang Dari angkasa dan Ke angkasa untuk pertukaran. Satu lagi perkara yang perlu dinyatakan ialah apabila menyalin objek, objek generasi baharu akan dipindahkan ke objek generasi lama.Objek yang dialihkan ini mempunyai syarat yang ditetapkan Terdapat dua jenis utama:
Kutipan sampah objek generasi lama akan menggunakan algoritma penandaan tambahan untuk mengoptimumkan proses pengumpulan sampah Algoritma penandaan tambahan ditunjukkan dalam rajah di bawah:
<.>Memandangkan JavaScript adalah satu utas, hanya satu pelaksanaan program dan pengumpulan sampah boleh dijalankan pada masa yang sama Ini akan menyebabkan program membeku apabila kutipan sampah dilaksanakan, yang pastinya akan memberikan pengalaman buruk kepada pengguna.
Jadi penandaan tambahan dicadangkan Apabila program berjalan, program mula-mula berjalan untuk tempoh masa, dan kemudian melakukan penandaan awal ini hanya boleh menandakan objek yang boleh dicapai secara langsung, dan kemudian program terus berjalan untuk satu tempoh masa, penandaan tambahan sedang dilakukan, iaitu, objek yang boleh dicapai secara tidak langsung ditanda. Ulangi ini sehingga tamat.
Memandangkan JavaScript tidak memberikan kami API untuk mengendalikan memori, kami hanya boleh bergantung pada pengurusan memori yang disediakan dengan sendirinya, tetapi kami tidak tahu apakah pengurusan memori sebenar suka. Kadangkala kita perlu memerhatikan penggunaan memori Alat Prestasi menyediakan pelbagai cara untuk memantau memori.
Mula-mula kami membuka penyemak imbas Chrome (di sini kami menggunakan penyemak imbas Chrome, pelayar lain juga boleh), masukkan alamat sasaran kami dalam bar alamat, dan kemudian Buka alat pembangun dan pilih panel [Prestasi].
Pilih panel prestasi dan hidupkan fungsi rakaman, kemudian akses antara muka tertentu, tiru pengguna untuk melakukan beberapa operasi, kemudian hentikan rakaman, dan akhirnya kita boleh menganalisis maklumat memori yang dirakam dalam antara muka analisis.
Manifestasi masalah ingatan
Masalah ingatan terutamanya mempunyai simptom berikut:
Berkenaan masalah ini, kita boleh menganalisis sebabnya melalui graf perubahan memori:
Masalah yang menyebabkan pengembangan memori mungkin merupakan masalah dengan kod kami, atau mungkin peranti itu sendiri lemah Jika kami ingin menganalisis, mencari dan menyelesaikannya, kami perlu menjalankan ujian berulang pada berbilang peranti
Untuk mengesan sama ada terdapat kebocoran memori, kita boleh memantau memori kita melalui Memory Total View Jika ingatan terus meningkat, kebocoran memori mungkin telah berlaku.
Terdapat terutamanya cara berikut untuk memantau memori dalam penyemak imbas:
Seterusnya kami akan menerangkan perkara ini secara berasingan Beberapa cara.
Tekan kekunci [Shift] [ESC] dalam penyemak imbas untuk membuka pengurus tugas yang disediakan oleh penyemak imbas Rajah berikut menunjukkan tugas dalam Pengurus penyemak imbas Chrome. mari kita tafsirkan
Dalam gambar di atas, kita dapat melihat bahawa [Memory Occupied Space] tab [Nuggets] mewakili memori yang diduduki oleh DOM halaman ini dalam penyemak imbas Jika ia berterusan Bertambah bermakna DOM baharu sedang dibuat; dan [Memori yang digunakan oleh JavaScript] berikut (tidak didayakan secara lalai, perlu dibuka dengan mengklik kanan) mewakili timbunan dalam JavaScript dan saiz dalam kurungan mewakili semua objek yang boleh dicapai dalam JavaScript .
Pengurus tugas yang disediakan dalam penyemak imbas yang diterangkan di atas hanya boleh digunakan untuk membantu kami menentukan sama ada terdapat masalah dengan halaman, tetapi ia tidak dapat mengesan masalah dengan halaman .
Garis masa ialah tab kecil dalam alat Prestasi, yang merekodkan situasi pada halaman dalam milisaat, yang boleh membantu kami mencari masalah dengan lebih mudah.
Timbunan gambar sangat disasarkan untuk mencari sama ada terdapat beberapa DOM yang dipisahkan dalam objek antara muka semasa Kewujudan DOM yang dipisahkan bermakna terdapat kebocoran memori.
Pertama sekali, kita perlu memikirkan beberapa keadaan DOM:
Langkah-langkah untuk mencari DOM yang dipisahkan: Buka alatan pembangun → [Panel Memori] → [Konfigurasi Pengguna] → [Dapatkan Syot Kilat] → Masukkan Detached
dalam [Penapis] untuk mencari DOM yang dipisahkan
Seperti yang ditunjukkan di bawah:
Selepas mencari DOM berasingan yang dibuat, kami mencari rujukan kepada DOM dan kemudian melepaskannya.
Kerana aplikasi berhenti apabila GC
berfungsi Jika kutipan sampah semasa berfungsi dengan kerap dan mengambil masa terlalu lama, ia akan menjadi sangat tidak mesra halaman . , yang akan menyebabkan aplikasi kelihatan mati dan pengguna akan melihat aplikasi tersekat semasa digunakan.
Kita boleh menilai sama ada terdapat kutipan sampah yang kerap dengan cara berikut, seperti berikut:
[Cadangan berkaitan: tutorial video javascript, bahagian hadapan web]
Atas ialah kandungan terperinci Pemahaman mendalam tentang pengurusan memori JavaScript dan algoritma GC. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!