ประโยชน์ที่ใหญ่ที่สุดประการหนึ่งของการใช้ภาษาสคริปต์ก็คือ คุณสามารถใช้ประโยชน์จากกลไกการรวบรวมขยะอัตโนมัติ (การเพิ่มหน่วยความจำ) คุณไม่จำเป็นต้องดำเนินการใดๆ เพื่อปล่อยหน่วยความจำหลังจากใช้ตัวแปร PHP จะช่วยคุณเอง
แน่นอนว่าเราสามารถเรียกใช้ฟังก์ชัน unset() เพื่อเพิ่มหน่วยความจำได้หากต้องการ แต่โดยปกติแล้วไม่จำเป็นต้องทำเช่นนั้น
อย่างไรก็ตาม ใน PHP มีอย่างน้อยหนึ่งสถานการณ์ที่หน่วยความจำจะไม่ถูกปล่อยออกมาโดยอัตโนมัติ แม้ว่าจะเรียกใช้ unset() ด้วยตนเองก็ตาม ดูรายละเอียดได้ที่: http://bugs.php.net/bug.php?id=33595
อาการของปัญหา: หากมีความสัมพันธ์การอ้างอิงร่วมกันระหว่างสองวัตถุ เช่น "วัตถุหลัก-วัตถุลูก" การเรียก unset() บนวัตถุหลักจะไม่ปล่อยหน่วยความจำที่อ้างอิงถึงวัตถุหลักในวัตถุลูก (แม้ว่าวัตถุหลัก วัตถุถูกรวบรวมขยะ และใช้งานไม่ได้เช่นกัน)
สับสนเล็กน้อย? ลองดูโค้ดต่อไปนี้:
<?phpclass Foo {function __construct(){$this->bar = new Bar($this);}}class Bar {function __construct($foo = null){$this-> foo = $foo;}} while (true) {$foo = new Foo();unset($foo);echo number_format(memory_get_usage()) . "n";}?>เรียกใช้โค้ดนี้แล้วคุณจะเห็นการใช้หน่วยความจำ สูงขึ้นเรื่อยๆจนหมดแรง
...33,551,61633,551,97633,552,33633,552,696PHP ข้อผิดพลาดร้ายแรง: ขนาดหน่วยความจำที่อนุญาตคือ 33554432 ไบต์หมด (พยายามจัดสรร 16 ไบต์) ใน memleak.php ออนไลน์ 17 สำหรับโปรแกรมเมอร์ PHP ส่วนใหญ่ นี่คือสถานการณ์นี้ ไม่ใช่ปัญหา
แต่ถ้าคุณใช้วัตถุจำนวนมากที่อ้างอิงถึงกันในโค้ดที่ใช้เวลานาน โดยเฉพาะอย่างยิ่งถ้าวัตถุมีขนาดค่อนข้างใหญ่ หน่วยความจำจะหมดอย่างรวดเร็ว
โซลูชัน Userland ค่อนข้างน่าเบื่อและไม่สวยงาม แต่ลิงก์ bugs.php.net ที่กล่าวถึงก่อนหน้านี้เป็นวิธีแก้ปัญหา
โซลูชันนี้ใช้วิธีการทำลายล้างก่อนที่จะปล่อยวัตถุเพื่อให้บรรลุเป้าหมายนี้ เมธอด Destructor สามารถล้างการอ้างอิงอ็อบเจ็กต์พาเรนต์ภายในทั้งหมดได้ ซึ่งหมายความว่าส่วนนี้ของหน่วยความจำที่อาจล้นออกมาสามารถถูกปล่อยออกมาได้
นี่คือโค้ด "หลังการแก้ไข":
<?phpclass Foo {function __construct(){$this->bar = new Bar($this);}function __destruct(){unset($this->bar);}}class Bar {function __construct($foo = null){$this->foo = $foo;}} while (true) {$foo = new Foo();$foo->__destruct();unset($foo);echo number_format(memory_get_usage()) . "n";}?>สังเกตวิธี Foo::__destruct() ใหม่และการเรียก $foo->__destruct() ก่อนที่จะปล่อยวัตถุ ตอนนี้โค้ดนี้แก้ปัญหาเรื่องการเพิ่มการใช้หน่วยความจำ ดังนั้นโค้ดจึงสามารถทำงานได้ดี
โซลูชันเคอร์เนล PHP?
เหตุใดหน่วยความจำล้นจึงเกิดขึ้น ฉันไม่เชี่ยวชาญในการวิจัยเคอร์เนล PHP แต่ฉันแน่ใจว่าปัญหานี้เกี่ยวข้องกับการนับการอ้างอิง
จำนวนการอ้างอิงของ $foo ที่อ้างอิงใน $bar จะไม่ลดลงเนื่องจากอ็อบเจ็กต์หลัก $foo ถูกปล่อยออกมา ในขณะนี้ PHP คิดว่าคุณยังต้องการอ็อบเจ็กต์ $foo ดังนั้นหน่วยความจำส่วนนี้จะไม่ถูกปล่อยออกมา . หรือดังนั้น
ความไม่รู้ของฉันแสดงให้เห็นจริงๆ ที่นี่ แต่แนวคิดทั่วไปคือ จำนวนการอ้างอิงไม่ลดลง ดังนั้นหน่วยความจำบางส่วนจึงไม่เคยถูกปลดปล่อย
ในลิงก์ bugs.php.net ที่กล่าวมาข้างต้น ฉันเห็นว่าการแก้ไขกระบวนการรวบรวมขยะจะทำให้ประสิทธิภาพการทำงานลดลงอย่างมาก และเนื่องจากฉันไม่ค่อยมีความรู้เกี่ยวกับการนับการอ้างอิงมากนัก ฉันจึงสันนิษฐานว่านี่เป็นเรื่องจริง
แทนที่จะเปลี่ยนกระบวนการรวบรวมขยะ ทำไมไม่ยกเลิกการตั้งค่า() เพื่อปล่อยอ็อบเจ็กต์ภายในล่ะ (หรือเรียก __destruct() เมื่อปล่อยวัตถุ?)
บางทีนักพัฒนาเคอร์เนล PHP สามารถเปลี่ยนแปลงกลไกการรวบรวมขยะได้ที่นี่หรือที่อื่น ๆ
อัปเดต: Martin Fjordvald กล่าวถึงในความคิดเห็นเกี่ยวกับแพทช์ที่เขียนโดย David Wang สำหรับการเก็บขยะ (จริงๆ แล้วดูเหมือน "ผ้าทั้งผืน" มากกว่า - ใหญ่มาก ดูข้อมูลการส่งออก CVS ที่ส่วนท้ายของอีเมลนี้เพื่อดูรายละเอียด) แน่นอน มีอยู่ (อีเมล) และได้รับความสนใจจากสมาชิกของชุมชนการพัฒนาเคอร์เนล PHP คำถามคือว่าควรใส่แพตช์นี้ลงใน PHP5.3 หรือไม่ และมันยังไม่ได้รับการรองรับมากนัก ฉันคิดว่าการประนีประนอมที่ดีคือการเรียก __destruct() วิธีการในวัตถุในฟังก์ชัน unset();