ส่วนนี้อธิบายคำศัพท์ทั่วไปที่ใช้ในการวิเคราะห์หน่วยความจำ และมีผลกับเครื่องมือสร้างโปรไฟล์หน่วยความจำแบบต่างๆ สำหรับภาษาต่างๆ
โปรดดูข้อกำหนดและประกาศที่อธิบายในเครื่องมือสร้างโปรไฟล์ฮีปของ Chrome DevTools หากคุณเคยใช้ Java, .NET หรือเครื่องมือสร้างโปรไฟล์หน่วยความจำอื่นๆ มาก่อนหน้านี้แล้ว นี่อาจเป็นการทบทวนบทเรียนก็ได้
ขนาดออบเจ็กต์
ให้ลองคิดว่าหน่วยความจำเป็นกราฟที่มีประเภทพื้นฐาน (เช่น ตัวเลขและสตริง) และออบเจ็กต์ (อาร์เรย์แบบเชื่อมโยง) ภาพอาจแสดงเป็นกราฟซึ่งมีจุดต่างๆ ที่เชื่อมต่อกันดังนี้
ออบเจ็กต์เก็บหน่วยความจำได้ 2 วิธีดังนี้
- เข้าทางตัวออบเจ็กต์โดยตรง
- โดยการระบุการอ้างอิงไปยังวัตถุอื่นๆ เพื่อป้องกันไม่ให้พนักงานเก็บขยะ (GC) ทิ้งวัตถุเหล่านั้นโดยอัตโนมัติ
เมื่อทำงานกับ Heap Profiler ใน DevTools (เครื่องมือสำหรับตรวจสอบปัญหาหน่วยความจำที่พบใน "Profiles") คุณอาจพบว่าคุณดูคอลัมน์ข้อมูลต่างๆ เพียงไม่กี่คอลัมน์ 2 รูปแบบที่โดดเด่นคือ ขนาดตื้น และขนาดที่คงไว้ แต่ขนาดเหล่านี้หมายถึงอะไร
ขนาดระดับออบเจ็กต์
ซึ่งก็คือขนาดของหน่วยความจำที่จัดเก็บโดยตัวออบเจ็กต์เอง
ออบเจ็กต์ JavaScript ทั่วไปมีหน่วยความจำบางส่วนที่สำรองไว้สำหรับคำอธิบายและจัดเก็บค่าทันที โดยปกติแล้ว เฉพาะอาร์เรย์และสตริงจะมีขนาดต่ำมาก อย่างไรก็ตาม สตริงและอาร์เรย์ภายนอกมักมีพื้นที่เก็บข้อมูลหลักอยู่ในหน่วยความจำของตัวแสดงผล ซึ่งจะแสดงเพียงออบเจ็กต์ Wrapper ขนาดเล็กบนฮีป JavaScript
หน่วยความจำโหมดแสดงภาพคือหน่วยความจำทั้งหมดของกระบวนการที่มีการแสดงผลหน้าเว็บที่ตรวจสอบ ซึ่งได้แก่ หน่วยความจำเนทีฟ + หน่วยความจำฮีพของ JS ของหน้าเว็บ + หน่วยความจำฮีป JS ของผู้ปฏิบัติงานที่เฉพาะทางทั้งหมดที่เริ่มต้นในหน้าเว็บดังกล่าว อย่างไรก็ตาม แม้แต่วัตถุขนาดเล็กก็อาจเก็บหน่วยความจำจำนวนมากได้ทางอ้อมด้วยการป้องกันไม่ให้วัตถุอื่นๆ ถูกกำจัดทิ้งโดยกระบวนการเก็บรวบรวมขยะอัตโนมัติ
ขนาดที่คงไว้
นี่คือขนาดของหน่วยความจำที่มีว่างลงเมื่อมีการลบออบเจ็กต์ออกไปพร้อมกับออบเจ็กต์ที่เกี่ยวข้องซึ่งถูกทำให้เข้าถึงจากรูท GC ไม่ได้
รูท GC ประกอบด้วยแฮนเดิลที่สร้างขึ้น (ทั้งในเครื่องหรือส่วนกลาง) เมื่ออ้างอิงจากโค้ดแบบเนทีฟไปยังออบเจ็กต์ JavaScript นอก V8 แฮนเดิลดังกล่าวทั้งหมดจะอยู่ในฮีพสแนปชอตในส่วนรูท GC > ขอบเขตแฮนเดิล และรูท GC > แฮนเดิลส่วนกลาง การอธิบายแฮนเดิลในเอกสารประกอบนี้โดยไม่เจาะลึกรายละเอียดเกี่ยวกับการใช้งานเบราว์เซอร์อาจทำให้สับสนได้ คุณไม่ต้องกังวลทั้งรูทและแฮนเดิล GC เลย
มีรูท GC ภายในจำนวนมากที่ไม่ค่อยน่าสนใจสำหรับผู้ใช้ จากมุมมองของแอปพลิเคชันมีรากดังต่อไปนี้
- ออบเจ็กต์ส่วนกลางของ Windows (ใน iframe แต่ละรายการ) มีช่องระยะทางในสแนปชอตฮีปซึ่งเป็นจำนวนการอ้างอิงพร็อพเพอร์ตี้บนเส้นทางการเก็บรักษาที่สั้นที่สุดจากหน้าต่าง
- แผนผัง DOM ของเอกสารซึ่งประกอบด้วยโหนด DOM ดั้งเดิมทั้งหมดที่เข้าถึงได้ด้วยการข้ามผ่านเอกสาร แต่ในบางส่วนอาจมี JS Wrapper แต่กรณีที่มี Wrapper จะมีการทำงานอยู่ในขณะที่เอกสารทำงาน
- บางครั้งระบบอาจเก็บรักษาออบเจ็กต์ไว้ตามบริบทโปรแกรมแก้ไขข้อบกพร่องและคอนโซลเครื่องมือสำหรับนักพัฒนาเว็บ (เช่น หลังจากการประเมินคอนโซล) สร้างฮีปสแนปชอตที่มีคอนโซลที่ชัดเจนและไม่มีเบรกพอยท์ที่ใช้งานอยู่ในโปรแกรมแก้ไขข้อบกพร่อง
กราฟหน่วยความจำจะเริ่มต้นด้วยราก ซึ่งอาจเป็นออบเจ็กต์ window
ของเบราว์เซอร์หรือออบเจ็กต์ Global
ของโมดูล Node.js คุณไม่ได้ควบคุมวิธี GC ของออบเจ็กต์รากนี้
อะไรก็ตามที่เข้าถึงไม่ได้จากรูทจะได้รับ GC
ต้นไม้ที่รักษาวัตถุ
ฮีปคือเครือข่ายของออบเจ็กต์ที่เชื่อมต่อกัน ในโลกทางคณิตศาสตร์ โครงสร้างนี้เรียกว่ากราฟหรือกราฟหน่วยความจำ กราฟสร้างขึ้นจากโหนดที่เชื่อมต่อกันด้วยขอบ ซึ่งทั้ง 2 โหนดได้รับป้ายกำกับ
- โหนด (หรือออบเจ็กต์) ติดป้ายกำกับโดยใช้ชื่อของฟังก์ชันตัวสร้างที่ใช้สร้างโหนด
- Edge จะมีป้ายกำกับโดยใช้ชื่อของพร็อพเพอร์ตี้
ดูวิธีบันทึกโปรไฟล์โดยใช้เครื่องมือสร้างโปรไฟล์ฮีป สิ่งที่สะดุดตาที่เราเห็นในการบันทึกใน Heap Profiler ด้านล่างได้แก่ ระยะทาง: ระยะทางจากรูท GC หากออบเจ็กต์เกือบทั้งหมดที่อยู่ในประเภทเดียวกันอยู่ห่างกัน และมี 2-3 รายการอยู่ห่างกันมากขึ้น นั่นเป็นสิ่งที่ควรตรวจสอบ
โดมิเนเตอร์
วัตถุโดมิเนเตอร์ประกอบด้วยโครงสร้างต้นไม้ เนื่องจากวัตถุแต่ละวัตถุมีโดมิเนเตอร์เพียงหนึ่งเดียว โดโดมิเนียร์ของออบเจ็กต์อาจขาดการอ้างอิงไปยังออบเจ็กต์ที่ควบคุมโดยตรง กล่าวคือ แผนผังของโดมิเนเตอร์ไม่ใช่แผนผังแบบกระจายของกราฟ
ในแผนภาพด้านล่าง
- โหนด 1 แทนที่โหนด 2
- โหนด 2 แทนที่โหนด 3, 4 และ 6
- โหนด 3 แทนที่โหนด 5
- โหนด 5 แทนที่โหนด 8
- โหนด 6 แทนที่โหนด 7
ในตัวอย่างด้านล่าง โหนด #3
คือ Dominator ของ #10
แต่ #7
มีอยู่ในทุกเส้นทางแบบง่ายจาก GC ไปยัง #10
ด้วย ดังนั้น วัตถุ B จะเป็นโดมิเนเตอร์ของวัตถุ A หากมี B อยู่ในเส้นทางแบบง่ายทุกเส้นทางตั้งแต่รูทถึงวัตถุ A
ข้อมูลจำเพาะ V8
เมื่อสร้างโปรไฟล์หน่วยความจำ คุณควรเข้าใจว่าเหตุใดฮีปสแนปชอตจึงมีลักษณะบางอย่าง ส่วนนี้จะอธิบายหัวข้อที่เกี่ยวข้องกับหน่วยความจำโดยเฉพาะอย่างยิ่ง ที่เกี่ยวข้องกับเครื่องเสมือน JavaScript V8 (V8 VM หรือ VM)
การแสดงออบเจ็กต์ของ JavaScript
มีประเภทพื้นฐาน 3 ประเภทดังนี้
- ตัวเลข (เช่น 3.14159..)
- บูลีน (จริงหรือเท็จ)
- สตริง (เช่น "เวิร์เนอร์ ไฮเซนเบิร์ก")
โดยอ้างอิงค่าอื่นๆ ไม่ได้และจะเป็น Leaf หรือเป็นจุดสิ้นสุดของโหนดเสมอ
หมายเลขสามารถจัดเก็บเป็นอย่างใดอย่างหนึ่งต่อไปนี้
- ค่าที่เป็นจำนวนเต็ม 31 บิตแบบทันทีที่เรียกว่าจำนวนเต็มขนาดเล็ก (SMI) หรือ
- ออบเจ็กต์ฮีป ซึ่งเรียกว่าจำนวนฮีป หมายเลขฮีปจะใช้สำหรับจัดเก็บค่าที่ไม่เข้ากับแบบฟอร์ม SMI เช่น ดับเบิล หรือเมื่อค่าจำเป็นต้องบรรจุกล่อง เช่น การตั้งค่าพร็อพเพอร์ตี้ในนั้น
สตริงสามารถจัดเก็บในที่ใดที่หนึ่งต่อไปนี้
- ฮีป VM หรือ
- ในหน่วยความจำของผู้แสดงผล ระบบจะสร้างออบเจ็กต์ wrapper ขึ้นและใช้เพื่อเข้าถึงพื้นที่เก็บข้อมูลภายนอก เช่น ระบบจัดเก็บแหล่งที่มาของสคริปต์และเนื้อหาอื่นๆ ที่ได้รับจากเว็บ แทนที่จะคัดลอกไปยังฮีป VM
หน่วยความจำสำหรับออบเจ็กต์ JavaScript ใหม่จะได้รับการจัดสรรจากฮีป JavaScript เฉพาะ (หรือฮีป VM) วัตถุเหล่านี้จัดการโดยระบบเก็บขยะของ V8 ดังนั้น วัตถุเหล่านี้จะยังคงอยู่ตราบเท่าที่ยังมีข้อมูลอ้างอิงที่สําคัญอย่างน้อย 1 รายการ
ออบเจ็กต์เนทีฟคือสิ่งอื่นๆ ทั้งหมดที่ไม่ได้อยู่ในฮีป JavaScript ออบเจ็กต์ดั้งเดิมแตกต่างจากออบเจ็กต์ฮีป จะไม่ได้รับการจัดการโดยโปรแกรมรวบรวมขยะ V8 ตลอดอายุการใช้งาน และจะเข้าถึงได้จาก JavaScript โดยใช้ออบเจ็กต์ Wrapper ของ JavaScript เท่านั้น
สตริงข้อเสียคือออบเจ็กต์ที่ประกอบด้วยคู่ของสตริงที่จัดเก็บไว้แล้วผนวก และเป็นผลลัพธ์ของการต่อ การรวมเนื้อหาสตริง Cons จะเกิดขึ้นเมื่อจำเป็นเท่านั้น เช่น เมื่อต้องการสตริงย่อยของสตริงที่ผนวก
เช่น หากเชื่อมต่อ a กับ b คุณจะได้สตริง (a, b) ซึ่งแสดงผลลัพธ์ของการต่อการเชื่อมต่อ หากคุณเชื่อมโยง d ต่อกับผลลัพธ์นั้นในภายหลัง คุณจะได้สตริง Cons อีกรายการ ((a,b), d)
Arrays - อาร์เรย์เป็นออบเจ็กต์ที่มีคีย์ตัวเลข ซึ่งมีการใช้อย่างแพร่หลายใน VM ของ V8 เพื่อจัดเก็บข้อมูลจำนวนมาก ชุดคีย์-ค่าที่ใช้เหมือนพจนานุกรมจะสำรองข้อมูลด้วยอาร์เรย์
ออบเจ็กต์ JavaScript โดยทั่วไปอาจเป็นอาร์เรย์ 1 ใน 2 ประเภทที่ใช้จัดเก็บ ดังนี้
- พร็อพเพอร์ตี้ที่มีชื่อ และ
- องค์ประกอบตัวเลข
ในกรณีที่พร็อพเพอร์ตี้มีจำนวนน้อยมาก ระบบอาจจัดเก็บพร็อพเพอร์ตี้ดังกล่าวไว้ภายในตัวออบเจ็กต์ JavaScript เอง
จับคู่ - ออบเจ็กต์ที่อธิบายประเภทและเลย์เอาต์ของออบเจ็กต์ เช่น การใช้แผนที่เพื่ออธิบายลำดับชั้นของวัตถุโดยนัยสำหรับการเข้าถึงที่พักอย่างรวดเร็ว
กลุ่มออบเจ็กต์
กลุ่มออบเจ็กต์ดั้งเดิมแต่ละกลุ่มสร้างขึ้นจากออบเจ็กต์ที่มีการอ้างอิงซึ่งกันและกัน ตัวอย่างเช่น ต้นไม้ย่อย DOM ที่ทุกโหนดมีลิงก์ไปยังระดับบนสุด และลิงก์ไปที่ย่อยถัดไปและข้างเคียงข้างเคียง ซึ่งจะสร้างกราฟที่เชื่อมต่อกัน โปรดทราบว่าออบเจ็กต์เนทีฟจะไม่แสดงในฮีป JavaScript จึงทำให้มีขนาดเป็น 0 แต่จะสร้างออบเจ็กต์ Wrapper แทน
ออบเจ็กต์ Wrapper แต่ละรายการมีการอ้างอิงไปยังออบเจ็กต์เนทีฟที่เกี่ยวข้องเพื่อเปลี่ยนเส้นทางคำสั่งไปยังออบเจ็กต์ดังกล่าว ในกลุ่มออบเจ็กต์จะมีออบเจ็กต์ Wrapper อย่างไรก็ตาม วิธีนี้ไม่ได้สร้างวัฏจักรที่รวบรวมไม่ได้ เนื่องจาก GC มีประสิทธิภาพเพียงพอที่จะปล่อยกลุ่มออบเจ็กต์ที่ไม่มีการอ้างอิง Wrapper อีกต่อไป แต่การไม่ปล่อย Wrapper เดียวจะทำให้ทั้งกลุ่มและ Wrapper ที่เกี่ยวข้อง