Artikel ini akan memperkenalkan anda kepada pengurusan memori dan algoritma pengumpulan sampah enjin V8 saya harap ia akan membantu anda!
Seperti yang kita sedia maklum, JS menguruskan kutipan sampah secara automatik dan pembangun tidak perlu mengambil berat tentang peruntukan memori dan kitar semula. Selain itu, mekanisme kutipan sampah juga merupakan bahagian yang biasa diuji dalam temu bual hadapan. Artikel ini menerangkan algoritma kutipan sampah generasi V8. Saya harap rakan-rakan selepas membaca artikel ini dapat memahami V8
(haha, itu 痛彻
!!!) tentang 痛彻
mekanisme pengumpulan sampah meliputi kandungan berikut : Had memori dan penyelesaian untuk
V8
Scavenge
algoritma untuk objek memori generasi baharu 可达性分析算法
menanda objek hidup dan Pengoptimuman bermakna Scavenge
Perbezaan pertama dalam/keluasan algoritma GC
sebab STW
dan strategi pengoptimumanV8 pada asalnya direka untuk penyemak imbas, dan terdapat beberapa senario di mana memori besar digunakan Terdapat sekatan pada penggunaan memori secara lalai dalam reka bentuk, dan hanya sebahagian daripada memori dibenarkan digunakan. Sistem 64-bit boleh membenarkan penggunaan kira-kira 1.4g ingatan untuk sistem 32-bit. Seperti yang ditunjukkan dalam kod berikut, semak kaedah had memori enjin V8 bergantung dalam Node:
process.memoryUsage(); // 返回内存的使用量,单位字节 { rss: 22953984, // 申请的总的堆内存 heapTotal: 9682944, // 已使用的堆内存 heapUsed: 5290344, external: 9388 }
V8
Ada satu lagi sebab penting untuk mengehadkan penggunaan memori . , apabila memori timbunan terlalu besar, ia akan mengambil masa V8
lebih lama untuk melaksanakan kutipan sampah (1.5g
hingga 50ms
), dan kutipan sampah bukan tambahan akan mengambil masa yang lebih lama (1.5g
hingga 1s
) . Selepas menerangkan mekanisme kutipan sampah V8
nanti, saya percaya semua orang akan dapat mengaitkannya dengan lebih lanjut.
Walaupun enjin V8
mempunyai had penggunaan memori, kaedah mengubah suai had memori juga didedahkan, iaitu menambah parameter yang berkaitan apabila menghidupkan enjin V8
Kod berikut menunjukkan pengubahsuaian bergantung < dalam Node
🎜>Had memori enjin: V8
# 更改老生代的内存限制,单位mb node --max-old-space-size=2048 index.js # 更改新生代的内存限制,单位mb node --max-semi-space-size=1024=64 index.js
kepada kb
, yang lama Kaedah penulisan ialah mb
Anda boleh menanyakan sintaks mengubah suai memori generasi baharu dalam persekitaran node --max-new-space-size
semasa melalui arahan berikut: Node
node --v8-options | grep max
generasi berdasarkan masa hidup objek Algoritma pengumpulan sampah generasi melaksanakan kaedah yang berbeza untuk jenis memori yang berbeza. algoritma kitar semula.
Bahagi memori kepada dua jenis: V8
dan 新生代
: 老生代
), dan ingatan generasi lama disimpan dalam ruang memori generasi lama (semispace
), seperti ditunjukkan dalam rajah di bawah: oldspace
Scavenge
Mark-Sweep
AlgoritmaMark-Compact
! Scavenge
untuk kitar semula memori memori generasi baharu Pelaksanaan khusus Scavenge
menggunakan algoritma Scavenge
. Algoritma Cheney
membahagikan ruang memori generasi baharu kepada dua, dengan satu ruang sedang digunakan (Cheney
) dan satu ruang dalam keadaan bebas (dipanggil FromSpace
). ToSpace
在内存开始分配时,首先在FromSpace
中进行分配,垃圾回收机制执行时会检查FromSpace
中的存活对象,存活对象会被会被复制到ToSpace
,非存活对象所占用的空间将被释放,复制完成后FromSpace
和ToSpace
的角色将翻转。当一个对象多次复制后依然处于存活状态,则认为其是长期存活对象,此时将发生晋升,然后该对象被移动到老生代空间oldSpace
中,采用新的算法进行管理。
Scavenge
算法其实就是在两个空间内来回复制存活对象,是典型的空间换时间做法,所以非常适合新生代内存,因为仅复制存活的对象且新生代内存中存活对象是占少数的。但是有如下几个重要问题需要考虑:
假设存在三个对象temp1、temp2、temp3
,其中temp2、temp3
都引用了temp1
,js代码示例如下:
var temp2 = { ref: temp1, } var temp3 = { ref: temp1, } var temp1 = {}
从FromSpace
中拷贝temp2
到ToSpace
中时,发现引用了temp1
,便把temp1
也拷贝到ToSpace
,是一个递归的过程。但是在拷贝temp3
时发现也引用了temp1
,此时再把temp1
拷贝过去则重复了。
要避免重复拷贝,做法是拷贝时给对象添加一个标记visited
表示该节点已被访问过,后续通过visited
属性判断是否拷贝对象。
还是上述引用关系,由于temp1
不需要重复拷贝,temp3
被拷贝到ToSpace
之后不知道temp1
对象在ToSpace
中的内存地址。
做法是temp1
被拷贝过去后该对象节点上会生成新的field
属性指向新的内存空间地址,同时更新到旧内存对象的forwarding
属性上,因此temp3
就可以通过旧temp1
的forwarding
属性找到在ToSpace
中的引用地址了。
内存对象同时存在于新生代和老生代之后,也带来了问题:
const temp1 = {} const temp2 = { ref: temp1, }
比如上述代码中的两个对象temp1
和temp2
都存在于新生代,其中temp2
引用了temp1
。假设在经过GC
之后temp2
晋升到了老生代,那么在下次GC
的标记阶段,如何判断temp1
是否是存活对象呢?
在基于可达性分析算法中要知道temp1
是否存活,就必须要知道是否有根对象引用
引用了temp1
对象。如此的话,年轻代的GC
就要遍历所有的老生代对象判断是否有根引用对象引用了temp1
对象,如此的话分代算法就没有意义了。
解决版本就是维护一个记录所有的跨代引用的记录集,它是写缓冲区
的一个列表。只要有老生代中的内存对象指向了新生代内存对象时,就将老生代中该对象的内存引用记录到记录集中。由于这种情况一般发生在对象写的操作,顾称此为写屏障,还一种可能的情况就是发生在晋升时。记录集的维护只要关心对象的写操作和晋升操作即可。此是又带来了另一个问题:
优化的手段是在一些Crankshaft
操作中是不需要写屏障的,还有就是栈上内存对象的写操作是不需要写屏障的。还有一些,更多的手段就不在这里过多讨论。
Scavenge
算法内存利用率不高问题新生代内存中存活对象占比是相对较小的,因此可以在分配空间时,ToSpace
可以分配的小一些。做法是将ToSpace
空间分成S0
和S1
两部分,S0
用作于ToSpace
,S1
与原FromSpace
合并当成FromSpace
。
垃圾回收算法中,识别内存对象是否是垃圾的机制一般有两种:引用计数和基于可达性分析。
Berdasarkan analisis kebolehcapaian, ia adalah untuk mencari semua rujukan akar (seperti pembolehubah global, dll.), merentasi semua rujukan akar dan semua rujukan pada rujukan akar rekursif Semua yang dilalui adalah Objek hidup dan tandainya Pada masa ini, objek ingatan lain dalam ruang adalah objek mati, dengan itu membina graf terarah.
Memandangkan pengehadan rekursif, logik rekursif secara amnya menggunakan pelaksanaan bukan rekursif yang biasa termasuk algoritma keluasan-didahulukan dan kedalaman-didahulukan. Perbezaan antara keduanya ialah:
ToSpace
, menjadikan objek dengan hubungan rujukan lebih rapat. Sebabnya ialah selepas menyalin dirinya sendiri, objek yang dirujuk dengan sendirinya disalin secara langsung, jadi objek yang berkaitan lebih rapat bersama dalam ToSpace
kerana daripada Strategi cache CPU, apabila membaca objek memori, terdapat kebarangkalian tinggi bahawa objek di belakangnya akan dibaca bersama, untuk memukul cache dengan lebih cepat. Kerana senario yang sangat biasa semasa pembangunan kod ialah obj1.obj2.obj3
Apabila CPU membaca obj1
, jika ia membaca obj2
dan obj3
berikut bersama-sama, ia akan sangat membantu untuk menekan cache.
Jadi algoritma depth-first adalah lebih kondusif untuk logik perniagaan mencecah cache, tetapi pelaksanaannya memerlukan pelaksanaan algoritme berbantu tindanan tambahan, yang menggunakan ruang memori. Breadth terlebih dahulu, sebaliknya, tidak dapat meningkatkan hits cache, tetapi pelaksanaannya boleh menggunakan penunjuk untuk mengelakkan penggunaan ruang dengan bijak, dan kecekapan pelaksanaan algoritma adalah tinggi.
Jika objek memori dalam generasi baharu ingin dinaikkan pangkat kepada generasi lama, mereka perlu memenuhi syarat berikut:
Scavenge
kitar semulaToSpace
nisbah penggunaan memori tidak boleh melebihi hadLogik untuk menentukan sama ada ia telah mengalami GC Scavenge
ialah, Setiap kali GC
diberikan kepada atribut age
1
objek yang masih hidup, dan apabila ia GC
sekali lagi, nilaikan atribut age
. Gambar rajah promosi asas adalah seperti berikut:
Terdapat banyak objek jangka panjang yang masih hidup dalam ingatan generasi lama, dan sebab mengapa algoritma Scavenge
tidak boleh dikitar semula ialah :
Pengumpulan sampah ruang memori generasi lama menggunakan gabungan 标记清除
(Mark-Sweep
) dan 标记整理
(Mark-Compact
). Menanda dan mengosongkan dibahagikan kepada dua bahagian:
Melintasi pelajar lama dalam fasa penandaan Semua objek memori dalam memori timbunan dijana dan objek hidup ditanda Fasa pembersihan hanya membersihkan objek yang tidak ditanda. Sebabnya ialah: objek bukan hidup menyumbang minoriti dalam ingatan generasi lama.
Seperti yang ditunjukkan dalam rajah di atas, satu masalah dengan pembersihan tanda ialah selepas pembersihan, terdapat ruang terputus yang tidak boleh digunakan lagi, jadi pembersihan memori memori generasi lama ruang diperlukan Penyelesaian yang digabungkan dengan penandaan. Penyelesaian ini adalah untuk mengalihkan objek hidup ke satu sisi semasa proses penandaan, dan kemudian membersihkan dan mengalih keluar semua objek bukan hidup di luar sempadan selepas pergerakan selesai.
Perlu menjeda logik pelaksanaan aplikasi semasa kutipan sampah, dan kemudian menyambung semula aplikasi selepas mekanisme kutipan sampah selesai Logik pelaksanaan, tingkah laku ini dipanggil "Jeda Penuh", yang sering dipanggil Stop The World
, atau STW
singkatannya. Pengumpulan sampah memori generasi muda mempunyai sedikit kesan ke atas pelaksanaan aplikasi, tetapi kerana terdapat banyak objek yang masih hidup dalam memori generasi lama, kesan jeda penuh yang disebabkan oleh kutipan sampah memori generasi lama adalah sangat besar.
Untuk mengoptimumkan masa jeda penuh GC, V8 turut memperkenalkan 增量标记
, 并发标记
, 并行标记
, 增量整理
, 并行清理
, 延迟清理
dsb.
Metrik penting untuk mengukur masa yang dihabiskan dalam kutipan sampah ialah jumlah masa utas utama dijeda semasa melaksanakan GC
. Kesan STW tidak boleh diterima, jadi V8 juga telah menggunakan banyak kaedah pengoptimuman.
Proses GC perlu melakukan banyak perkara, yang akan membawa kepada fenomena STW pada utas utama Kaedah GC selari adalah dengan membuka pelbagai tambahan benang untuk berkongsi kerja GC. Pendekatan ini masih tidak dapat mengelakkan fenomena STW, tetapi ia boleh mengurangkan jumlah masa STW, bergantung pada bilangan utas tambahan yang didayakan.
GC
GC Inkremental membahagikan kerja GC dan mengedarkannya secara berselang-seli dalam pelaksanaan langkah urutan utama. Pendekatan ini tidak akan mengurangkan masa GC Sebaliknya, ia akan menelan kos sedikit, tetapi ia juga akan mengurangkan jumlah masa STW GC.
GC
Concurrent GC bermaksud GC berjalan di latar belakang dan tidak lagi berjalan pada utas utama. Pendekatan ini akan mengelakkan fenomena STW. Paparan animasi dalam
GC
Chrome
adalah lebih kurang 60
bingkai (setiap bingkai adalah lebih kurang 16ms
), jika masa pemaparan semasa mencapai 16.6ms
, akan ada masa terluang untuk melakukan perkara lain, seperti beberapa GC
tugasan.
Untuk meningkatkan kecekapan pelaksanaan, meminimumkan pelaksanaan dan penggunaan kutipan sampah:
Berhati-hati apabila menggunakan memori sebagai cache, dan berhati-hati menggunakan objek sebagai cache Untuk mengehadkan masa tamat tempoh dan isu pertumbuhan yang tidak terhingga secara munasabah, anda boleh menggunakan strategi lru
Elakkan menggunakan memori untuk menyimpan sesi pengguna Jika tidak, menyimpan sejumlah besar objek sesi pengguna dalam memori akan menyebabkan memori generasi lama melonjak, menjejaskan prestasi pembersihan dan dengan itu menjejaskan pelaksanaan aplikasi. prestasi dan limpahan ingatan. Cara yang lebih baik untuk menggunakan redis, dsb. Faedah mengalihkan cache secara luaran: Node
tutorial nodejs!
Atas ialah kandungan terperinci Mari kita bincangkan tentang pengurusan memori V8 dan algoritma pengumpulan sampah. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!