การจัดการหน่วยความจำใน JavaScript ดำเนินการโดยอัตโนมัติและมองไม่เห็นสำหรับเรา เราสร้างวัตถุพื้นฐาน วัตถุ ฟังก์ชั่น... ทั้งหมดที่ต้องใช้ความทรงจำ
จะเกิดอะไรขึ้นเมื่อบางสิ่งไม่จำเป็นอีกต่อไป? เอ็นจิ้น JavaScript ค้นพบและทำความสะอาดได้อย่างไร
แนวคิดหลักของการจัดการหน่วยความจำใน JavaScript คือ ความสามารถในการเข้าถึง
พูดง่ายๆ ก็คือค่า "ที่เข้าถึงได้" คือค่าที่สามารถเข้าถึงได้หรือใช้งานได้ในทางใดทางหนึ่ง รับประกันว่าจะเก็บไว้ในหน่วยความจำ
มีชุดฐานของค่าที่เข้าถึงได้โดยธรรมชาติ ซึ่งไม่สามารถลบออกได้ด้วยเหตุผลที่ชัดเจน
ตัวอย่างเช่น:
ค่าเหล่านี้เรียกว่า ราก
ฟังก์ชันที่กำลังดำเนินการอยู่ ตัวแปรภายในเครื่องและพารามิเตอร์
ฟังก์ชันอื่นๆ บนสายโซ่ปัจจุบันของการโทรแบบซ้อน ตัวแปรภายในเครื่องและพารามิเตอร์
ตัวแปรโกลบอล
(ยังมีอย่างอื่นภายในด้วย)
ค่าอื่นๆ จะถือว่าสามารถเข้าถึงได้หากสามารถเข้าถึงได้จากรากโดยการอ้างอิงหรือโดยสายการอ้างอิง
ตัวอย่างเช่น หากมีวัตถุอยู่ในตัวแปรร่วม และวัตถุนั้นมีคุณสมบัติที่อ้างอิงถึงวัตถุอื่น วัตถุ นั้น จะถือว่าสามารถเข้าถึงได้ และสิ่งที่อ้างอิงก็สามารถเข้าถึงได้เช่นกัน ตัวอย่างรายละเอียดที่น่าติดตาม
มีกระบวนการเบื้องหลังในเอ็นจิ้น JavaScript ที่เรียกว่าตัวรวบรวมขยะ จะตรวจสอบวัตถุทั้งหมดและลบวัตถุที่ไม่สามารถเข้าถึงได้
นี่เป็นตัวอย่างที่ง่ายที่สุด:
// ผู้ใช้มีการอ้างอิงถึงวัตถุ ให้ผู้ใช้ = { ชื่อ: "จอห์น" -
ที่นี่ลูกศรแสดงถึงการอ้างอิงวัตถุ ตัวแปรโกลบอล "user"
อ้างอิงถึงวัตถุ {name: "John"}
(เราจะเรียกมันว่า John เพื่อความกระชับ) คุณสมบัติ "name"
ของ John เก็บค่าดั้งเดิม ดังนั้นจึงถูกทาสีไว้ภายในวัตถุ
ถ้าค่าของ user
ถูกเขียนทับ การอ้างอิงจะหายไป:
ผู้ใช้ = โมฆะ;
ตอนนี้จอห์นเข้าถึงไม่ได้ ไม่มีทางที่จะเข้าถึงมันได้ ไม่มีการอ้างอิงถึงมัน ตัวรวบรวมขยะจะขยะข้อมูลและเพิ่มหน่วยความจำ
ทีนี้ลองจินตนาการว่าเราคัดลอกการอ้างอิงจาก user
ไปยัง admin
:
// ผู้ใช้มีการอ้างอิงถึงวัตถุ ให้ผู้ใช้ = { ชื่อ: "จอห์น" - ให้ผู้ดูแลระบบ = ผู้ใช้;
ตอนนี้ถ้าเราทำเช่นเดียวกัน:
ผู้ใช้ = โมฆะ;
…จากนั้นวัตถุยังคงสามารถเข้าถึงได้ผ่านตัวแปรโกลบอล admin
ดังนั้นจึงต้องอยู่ในหน่วยความจำ หากเราเขียนทับ admin
ด้วยก็สามารถลบออกได้
ตอนนี้เป็นตัวอย่างที่ซับซ้อนมากขึ้น ครอบครัว:
ฟังก์ชั่น แต่งงาน (ชาย, หญิง) { woman.husband = ผู้ชาย; ผู้ชาย ภรรยา = ผู้หญิง; กลับ { พ่อ: ผู้ชาย แม่: ผู้หญิง - - ให้ครอบครัว = แต่งงานกัน ({ ชื่อ: "จอห์น" - ชื่อ:แอน -
ฟังก์ชั่น marry
"แต่งงาน" สองวัตถุโดยให้วัตถุทั้งสองอ้างอิงถึงกันและส่งกลับวัตถุใหม่ที่มีทั้งสองวัตถุ
โครงสร้างหน่วยความจำผลลัพธ์:
ณ ขณะนี้สามารถเข้าถึงวัตถุทั้งหมดได้
ตอนนี้เรามาลบการอ้างอิงสองตัว:
ลบ family.father; ลบ family.mother.husband;
การลบข้อมูลอ้างอิงเพียงรายการเดียวจากสองรายการเหล่านี้ไม่เพียงพอ เนื่องจากออบเจ็กต์ทั้งหมดยังคงสามารถเข้าถึงได้
แต่ถ้าเราลบทั้งสองอย่าง เราจะเห็นว่า John ไม่มีการอ้างอิงเข้ามาอีกต่อไป:
การอ้างอิงขาออกไม่สำคัญ เฉพาะรายการที่เข้ามาเท่านั้นที่สามารถทำให้วัตถุเข้าถึงได้ ดังนั้น ตอนนี้ John ไม่สามารถเข้าถึงได้ และจะถูกลบออกจากหน่วยความจำพร้อมกับข้อมูลทั้งหมดที่ไม่สามารถเข้าถึงได้เช่นกัน
หลังจากเก็บขยะ:
เป็นไปได้ว่าเกาะทั้งหมดของวัตถุที่เชื่อมโยงกันจะไม่สามารถเข้าถึงได้และถูกลบออกจากหน่วยความจำ
วัตถุต้นทางเหมือนกับด้านบน แล้ว:
ครอบครัว = โมฆะ;
รูปภาพในหน่วยความจำจะกลายเป็น:
ตัวอย่างนี้แสดงให้เห็นว่าแนวคิดเรื่องความสามารถในการเข้าถึงมีความสำคัญเพียงใด
เห็นได้ชัดว่า John และ Ann ยังคงเชื่อมโยงกัน ทั้งคู่มีข้อมูลอ้างอิงที่เข้ามาแล้ว แต่นั่นยังไม่เพียงพอ
วัตถุ "family"
เดิมได้ถูกยกเลิกการเชื่อมโยงจากรากแล้ว ไม่มีการอ้างอิงถึงมันอีกต่อไป ดังนั้นทั้งเกาะจึงไม่สามารถเข้าถึงได้และจะถูกลบออก
อัลกอริธึมการรวบรวมขยะพื้นฐานเรียกว่า "ทำเครื่องหมายแล้วกวาด"
มีการดำเนินการตามขั้นตอน "การเก็บขยะ" ต่อไปนี้เป็นประจำ:
คนเก็บขยะหยั่งรากและ "ทำเครื่องหมาย" (จดจำ) พวกเขา
จากนั้นจะเข้าชมและ "ทำเครื่องหมาย" การอ้างอิงทั้งหมดจากพวกเขา
จากนั้นจะไปที่วัตถุที่ทำเครื่องหมายไว้และทำเครื่องหมาย การ อ้างอิง วัตถุที่เยี่ยมชมทั้งหมดจะถูกจดจำ เพื่อไม่ให้เยี่ยมชมวัตถุเดียวกันซ้ำอีกในอนาคต
…และต่อๆ ไปจนกว่าจะมีการเยี่ยมชมข้อมูลอ้างอิงที่เข้าถึงได้ (จากราก) ทุกรายการ
วัตถุทั้งหมดยกเว้นวัตถุที่ทำเครื่องหมายไว้จะถูกลบออก
ตัวอย่างเช่น ให้โครงสร้างวัตถุของเรามีลักษณะดังนี้:
เราจะเห็น “เกาะที่เข้าไม่ถึง” อย่างชัดเจนทางด้านขวา ตอนนี้เรามาดูกันว่าคนเก็บขยะแบบ "ทำเครื่องหมายแล้วกวาด" จัดการกับมันอย่างไร
ขั้นตอนแรกบ่งบอกถึงราก:
จากนั้นเราติดตามข้อมูลอ้างอิงและทำเครื่องหมายวัตถุที่อ้างอิง:
…และติดตามการอ้างอิงเพิ่มเติมต่อไปในขณะที่เป็นไปได้:
ตอนนี้วัตถุที่ไม่สามารถเข้าถึงได้ในกระบวนการถือว่าไม่สามารถเข้าถึงได้และจะถูกลบออก:
นอกจากนี้เรายังสามารถจินตนาการถึงกระบวนการที่เป็นการพ่นถังสีขนาดใหญ่ออกจากราก ซึ่งไหลผ่านการอ้างอิงทั้งหมด และทำเครื่องหมายวัตถุที่สามารถเข้าถึงได้ทั้งหมด จากนั้นสิ่งที่ไม่มีเครื่องหมายจะถูกลบออก
นั่นคือแนวคิดเกี่ยวกับวิธีการรวบรวมขยะ เอ็นจิ้น JavaScript ใช้การปรับให้เหมาะสมหลายอย่างเพื่อให้ทำงานเร็วขึ้น และไม่ทำให้เกิดความล่าช้าในการเรียกใช้โค้ด
การเพิ่มประสิทธิภาพบางส่วน:
คอลเลกชันตามรุ่น - วัตถุแบ่งออกเป็นสองชุด: "ของใหม่" และ "ของเก่า" ในโค้ดทั่วไป ออบเจ็กต์จำนวนมากมีช่วงชีวิตที่สั้น: ปรากฏขึ้น ทำงานและตายอย่างรวดเร็ว ดังนั้นจึงสมเหตุสมผลที่จะติดตามออบเจ็กต์ใหม่และล้างหน่วยความจำจากวัตถุเหล่านั้นหากเป็นเช่นนั้น ผู้ที่มีชีวิตอยู่ได้นานพอจะ "แก่" และถูกตรวจน้อยลง
การรวบรวมแบบเพิ่มหน่วย – หากมีวัตถุจำนวนมาก และเราพยายามเดินและทำเครื่องหมายวัตถุทั้งหมดที่ตั้งค่าไว้พร้อมกัน อาจต้องใช้เวลาระยะหนึ่งและทำให้การดำเนินการเกิดความล่าช้าที่มองเห็นได้ ดังนั้นเครื่องยนต์จึงแยกวัตถุที่มีอยู่ทั้งชุดออกเป็นหลายส่วน แล้วค่อยเคลียร์ส่วนเหล่านี้ทีละส่วน มีการรวบรวมขยะขนาดเล็กจำนวนมากแทนที่จะเป็นทั้งหมด ซึ่งจำเป็นต้องมีการจัดทำบัญชีเพิ่มเติมระหว่างกันเพื่อติดตามการเปลี่ยนแปลง แต่เราได้รับความล่าช้าเล็กๆ น้อยๆ แทนที่จะเป็นเรื่องใหญ่
การรวบรวมเวลาว่าง – ตัวรวบรวมขยะจะพยายามทำงานเฉพาะในขณะที่ CPU ไม่ได้ใช้งานเท่านั้น เพื่อลดผลกระทบที่อาจเกิดขึ้นกับการดำเนินการ
มีการเพิ่มประสิทธิภาพและรสชาติอื่นๆ ของอัลกอริธึมการรวบรวมขยะ เท่าที่ฉันต้องการอธิบายในที่นี้ ฉันก็ต้องอดทนไว้ก่อน เพราะเอ็นจิ้นที่ต่างกันใช้การปรับแต่งและเทคนิคที่แตกต่างกัน และที่สำคัญยิ่งกว่านั้น สิ่งต่างๆ เปลี่ยนแปลงไปตามการพัฒนาของเครื่องยนต์ ดังนั้นการศึกษาให้ลึกยิ่งขึ้น "ล่วงหน้า" โดยไม่จำเป็นต้องมีความจำเป็นจริงๆ อาจไม่คุ้มกับสิ่งนั้น เว้นเสียแต่ว่ามันเป็นเรื่องของความสนใจอย่างแท้จริงก็จะมีลิงก์สำหรับคุณด้านล่าง
สิ่งสำคัญที่ควรรู้:
การรวบรวมขยะจะดำเนินการโดยอัตโนมัติ เราไม่สามารถบังคับหรือป้องกันได้
วัตถุจะถูกเก็บไว้ในหน่วยความจำในขณะที่สามารถเข้าถึงได้
การอ้างอิงนั้นไม่เหมือนกับการเข้าถึงได้ (จากราก): กลุ่มของอ็อบเจ็กต์ที่เชื่อมโยงกันอาจไม่สามารถเข้าถึงได้โดยรวม ดังที่เราได้เห็นในตัวอย่างด้านบน
เอ็นจิ้นสมัยใหม่ใช้อัลกอริธึมขั้นสูงในการรวบรวมขยะ
หนังสือทั่วไปเรื่อง “คู่มือการเก็บขยะ: ศิลปะแห่งการจัดการหน่วยความจำอัตโนมัติ” (อาร์ โจนส์ และคณะ) ครอบคลุมบางเล่ม
หากคุณคุ้นเคยกับการเขียนโปรแกรมระดับต่ำ ข้อมูลโดยละเอียดเพิ่มเติมเกี่ยวกับตัวรวบรวมขยะของ V8 อยู่ในบทความ ทัวร์ชม V8: Garbage Collection
บล็อก V8 ยังเผยแพร่บทความเกี่ยวกับการเปลี่ยนแปลงในการจัดการหน่วยความจำเป็นครั้งคราว โดยปกติแล้ว หากต้องการเรียนรู้เพิ่มเติมเกี่ยวกับการเก็บขยะ คุณควรเตรียมตัวโดยการเรียนรู้เกี่ยวกับระบบภายในของ V8 โดยทั่วไป และอ่านบล็อกของ Vyacheslav Egorov ซึ่งทำงานเป็นหนึ่งในวิศวกร V8 ฉันกำลังพูดว่า: "V8" เพราะบทความบนอินเทอร์เน็ตครอบคลุมได้ดีที่สุด สำหรับเอ็นจิ้นอื่นๆ หลายวิธีจะคล้ายกัน แต่การรวบรวมขยะแตกต่างกันในหลายๆ ด้าน
ความรู้เชิงลึกเกี่ยวกับเครื่องยนต์นั้นดีเมื่อคุณต้องการการปรับให้เหมาะสมในระดับต่ำ ควรวางแผนเป็นขั้นตอนต่อไปหลังจากที่คุณคุ้นเคยกับภาษาแล้ว