Garbage Collection เป็นกลไกที่ซ่อนอยู่ของ JavaScript
โดยปกติแล้วเราไม่จำเป็นต้องกังวลเกี่ยวกับการรวบรวมขยะ เราเพียงแค่มุ่งเน้นที่การพัฒนาฟังก์ชันเท่านั้น แต่นี่ไม่ได้หมายความว่าเราจะนั่งพักผ่อนได้เมื่อเขียน JavaScript
เนื่องจากฟังก์ชันที่เราใช้งานมีความซับซ้อนมากขึ้นเรื่อยๆ และจำนวนโค้ดก็สะสมมากขึ้น ปัญหาด้านประสิทธิภาพจึงโดดเด่นมากขึ้นเรื่อยๆ วิธีเขียนโค้ดที่ทำงานเร็วขึ้นและใช้หน่วยความจำน้อยลงคือสิ่งที่โปรแกรมเมอร์แสวงหาอย่างไม่สิ้นสุด โปรแกรมเมอร์ที่ยอดเยี่ยมสามารถบรรลุผลลัพธ์ที่น่าอัศจรรย์ได้เสมอด้วยทรัพยากรที่จำกัดอย่างยิ่ง นี่คือความแตกต่างระหว่างสิ่งมีชีวิตธรรมดาและเทพเจ้าที่ห่างไกล
เมื่อดำเนินการในหน่วยความจำของคอมพิวเตอร์ ตัวแปร วัตถุ และฟังก์ชันทั้งหมดที่เรากำหนดไว้ในโค้ดจะใช้พื้นที่หน่วยความจำจำนวนหนึ่งในหน่วยความจำ ในคอมพิวเตอร์ พื้นที่หน่วยความจำเป็นทรัพยากรที่จำกัดมาก เราต้องใส่ใจกับการใช้หน่วยความจำอยู่เสมอ ท้ายที่สุดแล้ว โมดูลหน่วยความจำก็มีราคาแพงมาก! ตัวแปร ฟังก์ชัน หรืออ็อบเจ็กต์สามารถเรียกได้ว่าเป็นขยะ หากไม่จำเป็นอีกต่อไปสำหรับการเรียกใช้โค้ดในภายหลังหลังจากการสร้าง
แม้ว่าจะเป็นเรื่องง่ายมากที่จะเข้าใจคำจำกัดความของขยะโดยสัญชาตญาณ แต่สำหรับโปรแกรมคอมพิวเตอร์ ก็เป็นเรื่องยากสำหรับเราที่จะสรุปในช่วงเวลาหนึ่งว่าตัวแปร ฟังก์ชัน หรืออ็อบเจ็กต์ที่มีอยู่ในปัจจุบันจะไม่ถูกนำมาใช้อีกต่อไปในอนาคต เพื่อลดต้นทุนของหน่วยความจำคอมพิวเตอร์และรับรองการทำงานตามปกติของโปรแกรมคอมพิวเตอร์ เรามักจะกำหนดว่าวัตถุหรือตัวแปรที่ตรงตามเงื่อนไขใดๆ ต่อไปนี้เป็นขยะ:
ตัวแปรหรือวัตถุที่ไม่ได้อ้างอิงจะเทียบเท่ากับบ้านที่ไม่มีประตู ดังนั้นจึงเป็นไปไม่ได้ที่จะใช้มัน แม้ว่าวัตถุที่ไม่สามารถเข้าถึงได้จะเชื่อมต่ออยู่ แต่ก็ยังไม่สามารถเข้าถึงได้จากภายนอก ดังนั้นจึงไม่สามารถใช้งานได้อีก ออบเจ็กต์หรือตัวแปรที่ตรงตามเงื่อนไขข้างต้นจะไม่ถูกนำมาใช้อีกในการดำเนินการโปรแกรมในอนาคต ดังนั้นจึงถือว่าปลอดภัยเสมือนเป็นการรวบรวมขยะ
เมื่อเราชี้แจงวัตถุที่ต้องทิ้งตามคำจำกัดความข้างต้น หมายความว่าไม่มีขยะในตัวแปรและวัตถุที่เหลือใช่หรือไม่
เลขที่! ขยะที่เราระบุในปัจจุบันเป็นเพียงส่วนหนึ่งของขยะทั้งหมด ยังคงมีขยะอื่น ๆ ที่ไม่ตรงตามเงื่อนไขข้างต้น แต่จะไม่ถูกนำมาใช้อีก
พูดได้ไหมว่าขยะที่ตรงตามคำจำกัดความข้างต้นคือ "ขยะสัมบูรณ์" และขยะอื่น ๆ ที่ซ่อนอยู่ในโปรแกรมคือ "ขยะเชิงสัมพันธ์"?
การรวบรวมขยะ ( GC,Garbage Collection
) มีหน้าที่รับผิดชอบในการรีไซเคิลตัวแปรและพื้นที่หน่วยความจำที่ไม่มีประโยชน์ซึ่งครอบครองระหว่างการทำงานของโปรแกรม ปรากฏการณ์ที่วัตถุยังคงอยู่ในหน่วยความจำแม้ว่าจะไม่สามารถนำมาใช้ได้อีกก็ตาม เรียกว่า หน่วยความจำรั่ว หน่วยความจำรั่วถือเป็นปรากฏการณ์ที่อันตรายมาก โดยเฉพาะอย่างยิ่งในโปรแกรมที่รันระยะยาว หากโปรแกรมมีหน่วยความจำรั่ว โปรแกรมก็จะกินพื้นที่หน่วยความจำมากขึ้นเรื่อยๆ จนกระทั่งหน่วยความจำหมด
สตริง อ็อบเจ็กต์ และอาร์เรย์ไม่มีขนาดคงที่ ดังนั้นการจัดสรรพื้นที่เก็บข้อมูลแบบไดนามิกสำหรับสตริง วัตถุ และอาร์เรย์จึงทำได้ก็ต่อเมื่อทราบขนาดเท่านั้น ทุกครั้งที่โปรแกรม JavaScript สร้างสตริง อาร์เรย์ หรืออ็อบเจ็กต์ ล่ามจะจัดสรรหน่วยความจำเพื่อจัดเก็บเอนทิตี เมื่อใดก็ตามที่หน่วยความจำถูกจัดสรรแบบไดนามิกเช่นนี้ ในที่สุดหน่วยความจำนั้นจะต้องถูกปล่อยให้ว่างเพื่อให้สามารถใช้งานได้อีกครั้ง มิฉะนั้น ตัวแปล JavaScript จะใช้หน่วยความจำที่มีอยู่ในระบบ ส่งผลให้ระบบหยุดทำงาน
กลไกการรวบรวมขยะของ JavaScript
จะตรวจสอบตัวแปรและวัตถุที่ไม่มีประโยชน์ (ขยะ) เป็นระยะ ๆ และปล่อยพื้นที่ที่พวกมันครอบครอง
ภาษาการเขียนโปรแกรมที่แตกต่างกันใช้กลยุทธ์การรวบรวมขยะที่แตกต่างกัน ตัวอย่างเช่น C++
ไม่มีกลไกการรวบรวมขยะ ทั้งหมดขึ้นอยู่กับทักษะของโปรแกรมเมอร์เอง ซึ่งทำให้ C++
เชี่ยวชาญได้ยากขึ้น JavaScript
ใช้ ความสามารถในการเข้าถึง เพื่อจัดการหน่วยความจำ แท้จริงแล้ว ความสามารถในการเข้าถึงหมายถึงสามารถเข้าถึงได้ ซึ่งหมายความว่าโปรแกรมสามารถเข้าถึงและใช้ตัวแปรและอ็อบเจ็กต์ในทางใดทางหนึ่งได้
JavaScript
ระบุชุดค่าที่สามารถเข้าถึงได้โดยธรรมชาติและค่าในชุดสามารถเข้าถึงได้โดยเนื้อแท้:
ข้างต้นเรียกว่า root ซึ่งเป็นโหนดบนสุดของแผนผังความสามารถในการเข้าถึง
ตัวแปรหรืออ็อบเจ็กต์จะถือว่าสามารถเข้าถึงได้หากตัวแปรรูทใช้โดยตรงหรือโดยอ้อม
กล่าวอีกนัยหนึ่ง ค่าสามารถเข้าถึงได้หากสามารถเข้าถึงได้ผ่านรูท (เช่น Abcde
)
ให้คน = { หนุ่มๆ:{ เด็กชาย1:{ชื่อ:'เสี่ยวหมิง'}, เด็กชาย2:{ชื่อ:'เซียวจุน'}, - สาวๆ:{ girls1:{ชื่อ:'xiaohong'}, girls2:{ชื่อ:'huahua'}, }};
โค้ดข้างต้นจะสร้างอ็อบเจ็กต์และกำหนดให้กับตัวแปร people
people
ประกอบด้วยอ็อบเจ็กต์ 2 รายการ ได้แก่ boys
และ girls
และ boys
และ girls
มีออบเจ็กต์ย่อย 2 รายการตามลำดับ นอกจากนี้ยังสร้างโครงสร้างข้อมูลที่มีความสัมพันธ์อ้างอิง 3
ระดับ (ไม่ว่าข้อมูลประเภทพื้นฐาน) ดังที่แสดงด้านล่าง:
ในหมู่พวกเขา โหนด people
สามารถเข้าถึงได้ตามธรรมชาติเนื่องจากเป็นตัวแปรส่วนกลาง โหนด boys
และ girls
สามารถเข้าถึงได้โดยอ้อมเนื่องจากมีการอ้างอิงโดยตรงจากตัวแปรส่วนกลาง boys1
, boys2
, girls1
และ girls2
ยังเป็นตัวแปรที่เข้าถึงได้ เนื่องจากตัวแปรโกลบอลใช้ทางอ้อม และสามารถเข้าถึงได้ผ่าน people.boys.boys
หากเราเพิ่มโค้ดต่อไปนี้หลังโค้ดด้านบน:
people.girls.girls2 = null; people.girls.girls1 = people.boys.boys2;
จากนั้น แผนภาพลำดับชั้นอ้างอิงด้านบนจะเป็นดังนี้:
ในหมู่พวกเขา girls1
และ girls2
กลายเป็นโหนดที่ไม่สามารถเข้าถึงได้เนื่องจากขาดการเชื่อมต่อจากโหนด grils
ซึ่งหมายความว่าพวกมันจะถูกรีไซเคิลโดยกลไกการเก็บขยะ
และหากในเวลานี้ เรารันโค้ดต่อไปนี้:
people.boys.boys2 = null;
ดังนั้น แผนภาพลำดับชั้นการอ้างอิงจะเป็นดังนี้:
ในขณะนี้ แม้ว่าโหนด boys
และโหนด boys2
จะถูกตัดการเชื่อมต่อแล้ว เนื่องจากความสัมพันธ์ในการอ้างอิงระหว่างโหนด boys2
และโหนด girls
แต่ boys2
ยังคงสามารถเข้าถึงได้และจะไม่ถูกนำกลับมาใช้ใหม่โดยกลไกการรวบรวมขยะ
แผนภาพการเชื่อมโยงข้างต้นพิสูจน์ว่าเหตุใดค่าเทียบเท่าของตัวแปรส่วนกลางจึงเรียกว่า รูท เนื่องจากในแผนภาพการเชื่อมโยง ค่าประเภทนี้มักจะปรากฏเป็นโหนดรูทของแผนผังความสัมพันธ์
ให้คน = { หนุ่มๆ:{ เด็กชาย1:{ชื่อ:'เสี่ยวหมิง'}, เด็กชาย2:{ชื่อ:'เซียวจุน'}, - สาวๆ:{ girls1:{ชื่อ:'xiaohong'}, girls2:{ชื่อ:'huahua'}, }};people.boys.boys2.แฟน = people.girls.girls1; //boys2 หมายถึง girls1people.girls.girls1.boyfriend = people.boys.boys2; //girls1 หมายถึง boys2
โค้ดด้านบนสร้างความสัมพันธ์ที่สัมพันธ์กันระหว่าง boys2
และ girls1
แผนภาพโครงสร้างความสัมพันธ์เป็นดังนี้:
ณ จุดนี้ ถ้าเราตัดการเชื่อมโยงระหว่าง boys
และ boys2
ออก :
ลบ
people.boys.boys2;
แน่นอนว่าไม่มีโหนดที่ไม่สามารถเข้าถึงได้
ณ จุดนี้ ถ้าเราตัดการเชื่อมต่อความสัมพันธ์ boyfriend
:
ลบ people.girls.girls1
แผนภาพความสัมพันธ์จะกลายเป็น:
ในเวลานี้ แม้ว่าจะยังมีความสัมพันธ์ girlfriend
ระหว่าง boys2
และ girls1
แต่ boys2
ก็กลายเป็นโหนดที่ไม่สามารถเข้าถึงได้ และจะถูกเรียกคืนโดยกลไกการรวบรวมขยะ
ให้คน = { หนุ่มๆ:{ เด็กชาย1:{ชื่อ:'เสี่ยวหมิง'}, เด็กชาย2:{ชื่อ:'เซียวจุน'}, - สาวๆ:{ girls1:{ชื่อ:'xiaohong'}, girls2:{ชื่อ:'huahua'}, }};ลบ people.boys;ลบ people.girls;
ไดอะแกรมลำดับชั้นอ้างอิงที่เกิดขึ้นจากโค้ดด้านบนเป็นดังนี้:
ในขณะนี้ แม้ว่ายังคงมีความสัมพันธ์อ้างอิงร่วมกันระหว่างออบเจ็กต์ภายในกล่องประ แต่ออบเจ็กต์เหล่านี้ก็ไม่สามารถเข้าถึงได้เช่นกัน และจะถูกลบโดยกลไกการรวบรวมขยะ โหนดเหล่านี้สูญเสียความสัมพันธ์กับ รูท และไม่สามารถเข้าถึงได้
6.สิ่งที่เรียกว่าการนับการอ้างอิงจะถูกนับทุกครั้งที่มีการอ้างอิงออบเจ็กต์ การเพิ่มการอ้างอิงจะเพิ่มขึ้นหนึ่งรายการ และการลบการอ้างอิงจะลดลงหนึ่งรายการ หากการอ้างอิงนั้น ตัวเลขจะกลายเป็น 0 ถือเป็นขยะ ดังนั้นการลบวัตถุเพื่อเรียกคืนหน่วยความจำ
ตัวอย่างเช่น:
ให้ผู้ใช้ = {ชื่อผู้ใช้:'xiaoming'}; //วัตถุถูกอ้างอิงโดยตัวแปรผู้ใช้ นับ +1 ให้ user2 = ผู้ใช้; //วัตถุถูกอ้างอิงโดยตัวแปรใหม่ และนับ +1 ผู้ใช้ = โมฆะ; //ตัวแปรไม่ได้อ้างอิงถึงวัตถุอีกต่อไป นับเป็น -1 ผู้ใช้2 = โมฆะ; //ตัวแปรไม่อ้างอิงถึงวัตถุอีกต่อไป เลขคี่ -1 //ในขณะนี้ จำนวนการอ้างอิงออบเจ็กต์คือ 0 และจะถูกลบ
แม้ว่าวิธีการนับการอ้างอิงดูสมเหตุสมผลมาก แต่ในความเป็นจริง มีช่องโหว่ที่ชัดเจนในกลไกการรีไซเคิลหน่วยความจำโดยใช้วิธีการนับการอ้างอิง
ตัวอย่างเช่น:
ให้เด็ก = {}; ให้สาว = {}; boy.friends = เด็กผู้หญิง; girl.boyfriend = เด็กชาย; เด็กชาย = โมฆะ; girl = null;
โค้ดด้านบนมีการอ้างอิงร่วมกันระหว่าง boy
และ girl
การนับจะลบการอ้างอิงใน boy
และ girl
และวัตถุทั้งสองจะไม่ถูกรีไซเคิล เนื่องจากการมีอยู่ของการอ้างอิงแบบวงกลม จำนวนการอ้างอิงของวัตถุที่ไม่ระบุชื่อทั้งสองจะไม่กลับไปเป็นศูนย์ ส่งผลให้หน่วยความจำรั่ว
มีแนวคิดของ ตัวชี้อัจฉริยะ ( shared_ptr
) ใน C++
โปรแกรมเมอร์สามารถใช้ตัวชี้อัจฉริยะเพื่อใช้ตัวทำลายวัตถุเพื่อปล่อยจำนวนอ้างอิง อย่างไรก็ตาม หน่วยความจำรั่วจะเกิดขึ้นในกรณีของการอ้างอิงแบบวงกลม
โชคดีที่ JavaScript
ได้นำกลยุทธ์ที่ปลอดภัยกว่ามาใช้ ซึ่งช่วยหลีกเลี่ยงความเสี่ยงที่หน่วยความจำจะรั่วในระดับที่มากขึ้น
เครื่องหมาย mark and sweep
เป็นอัลกอริธึมการรวบรวมขยะที่นำมาใช้โดยเอ็นจิ้น JavaScript
หลักการพื้นฐานของมันคือการเริ่มต้นจาก รู ท สำรวจความสัมพันธ์อ้างอิงระหว่างตัวแปรเป็นอันดับแรก และทำเครื่องหมาย (优秀员工徽章
) บนตัวแปรที่สำรวจ ในที่สุดวัตถุที่ไม่ได้ทำเครื่องหมายจะถูกลบ
กระบวนการพื้นฐานของอัลกอริธึมมีดังนี้:
2
จนกว่าจะไม่มีพนักงานที่เป็นเลิศใหม่เข้าร่วม ออบตัวอย่างเช่น:
หากมีความสัมพันธ์การอ้างอิงวัตถุในโปรแกรมของเราดังที่แสดงด้านล่าง:
เราจะเห็นได้ชัดเจนว่ามี "เกาะที่เข้าถึงได้" ทางด้านขวาของภาพทั้งหมด เริ่มจาก ต้นตอ เกาะนี้ไม่มีทางเข้าถึงได้ แต่คนเก็บขยะไม่มีมุมมองของพระเจ้าเหมือนเรา พวกเขาจะทำเครื่องหมายเฉพาะโหนดรูทว่าเป็นพนักงานที่โดดเด่นตามอัลกอริทึม
จากนั้นเริ่มจากพนักงานที่โดดเด่นและค้นหาโหนดทั้งหมดที่พนักงานดีเด่นอ้างถึง เช่น โหนดทั้งสามในช่องจุดในรูปด้านบน จากนั้นทำเครื่องหมายโหนดที่เพิ่งค้นพบว่าเป็นพนักงานดีเด่น
กระบวนการค้นหาและทำเครื่องหมายซ้ำจนกระทั่งทำเครื่องหมายโหนดทั้งหมดที่พบได้สำเร็จ
ในที่สุดก็ได้ผลลัพธ์ดังแสดงในรูปด้านล่าง:
เนื่องจากเกาะทางด้านขวายังไม่มีเครื่องหมายหลังจากรอบการดำเนินการอัลกอริทึมสิ้นสุดลง งานเก็บขยะจะไม่สามารถเข้าถึงโหนดเหล่านี้ได้ และจะถูกล้างในที่สุด
เด็กที่ได้ศึกษาโครงสร้างข้อมูลและอัลกอริธึมอาจแปลกใจที่พบว่านี่คือการข้ามกราฟ ซึ่งคล้ายกับอัลกอริธึมกราฟที่เชื่อมโยงกัน
การรวบรวมขยะเป็นงานขนาดใหญ่ โดยเฉพาะอย่างยิ่งเมื่อจำนวนโค้ดมีขนาดใหญ่มาก การดำเนินการอัลกอริธึมการรวบรวมขยะบ่อยครั้งจะลากการทำงานของโปรแกรมลงอย่างมาก อัลกอริธึม JavaScript
ได้ทำการเพิ่มประสิทธิภาพมากมายในการรวบรวมขยะเพื่อให้แน่ใจว่าโปรแกรมสามารถดำเนินการได้อย่างมีประสิทธิภาพในขณะเดียวกันก็รับประกันการดำเนินการรีไซเคิลตามปกติ
กลยุทธ์ที่นำมาใช้สำหรับการเพิ่มประสิทธิภาพการทำงานมักจะประกอบด้วยประเด็นต่อไปนี้:
โปรแกรม JavaScriptJavaScript
รักษาตัวแปรจำนวนมากไว้ในระหว่างการดำเนินการ และการสแกนตัวแปรเหล่านี้บ่อยครั้งจะทำให้เกิดค่าใช้จ่ายจำนวนมาก อย่างไรก็ตาม ตัวแปรเหล่านี้มีลักษณะเฉพาะของตัวเองในวงจรชีวิต ตัวอย่างเช่น ตัวแปรภายในเครื่องมักถูกสร้างขึ้น ใช้งานอย่างรวดเร็ว จากนั้นจึงละทิ้ง ในขณะที่ตัวแปรส่วนกลางจะใช้หน่วยความจำเป็นเวลานาน JavaScript
จัดการออบเจ็กต์ทั้งสองประเภทแยกกัน สำหรับตัวแปรในเครื่องที่สร้างขึ้น ใช้ และทิ้งอย่างรวดเร็ว ตัวรวบรวมขยะจะสแกนบ่อยครั้งเพื่อให้แน่ใจว่าตัวแปรเหล่านี้ได้รับการล้างอย่างรวดเร็วหลังจากสูญเสียการใช้งาน สำหรับตัวแปรที่เก็บหน่วยความจำไว้เป็นเวลานาน ให้ลดความถี่ในการตรวจสอบลง ซึ่งจะช่วยประหยัดค่าใช้จ่ายได้จำนวนหนึ่ง
แนวคิดส่วนเพิ่มเป็นเรื่องปกติมากในการเพิ่มประสิทธิภาพการทำงาน และยังสามารถใช้สำหรับการรวบรวมขยะได้อีกด้วย เมื่อจำนวนตัวแปรมีขนาดใหญ่มาก จะเห็นได้ชัดว่าต้องใช้เวลานานมากในการสำรวจตัวแปรทั้งหมดในคราวเดียวและออกเครื่องหมายพนักงานที่โดดเด่น ส่งผลให้เกิดความล่าช้าระหว่างการทำงานของโปรแกรม ดังนั้นกลไกจะแบ่งงานรวบรวมขยะออกเป็นหลาย ๆ งานย่อย และค่อย ๆ ดำเนินการงานเล็ก ๆ แต่ละงานในระหว่างการทำงานของโปรแกรม ซึ่งจะทำให้เกิดความล่าช้าในการกู้คืน แต่โดยปกติแล้วจะไม่ทำให้โปรแกรมล่าช้าอย่างเห็นได้ชัด
CPUCPU
ทำงานเสมอไปแม้ในโปรแกรมที่ซับซ้อน นี่เป็นสาเหตุหลักมาจาก CPU
ทำงานเร็วมากและ IO
อุปกรณ์ต่อพ่วงมักจะช้ากว่าหลายลำดับ ดังนั้นจึงเป็นความคิดที่ดีที่จะจัดเตรียมกลยุทธ์การรวบรวมขยะเมื่อ CPU
ทำงาน ไม่ได้ใช้งาน นี่เป็นวิธีการเพิ่มประสิทธิภาพที่มีประสิทธิภาพมากและโดยพื้นฐานแล้วจะไม่ส่งผลเสียต่อตัวโปรแกรม กลยุทธ์นี้คล้ายกับการอัพเกรดเวลาว่างของระบบ และผู้ใช้ไม่ได้ตระหนักถึงการดำเนินการในเบื้องหลังเลย
ภารกิจหลักของบทความนี้คือการยุติกลไกการรวบรวมขยะ กลยุทธ์ที่ใช้กันทั่วไป และวิธีการเพิ่มประสิทธิภาพ ไม่ได้มีจุดมุ่งหมายเพื่อให้ทุกคนมีความเข้าใจในเชิงลึกเกี่ยวกับหลักการทำงานของกลไกเบื้องหลัง
จากบทความนี้ คุณควรเข้าใจ:
JavaScript
ซึ่งดำเนินการในเบื้องหลังและไม่ต้องการให้เรากังวลเกี่ยวกับมัน