สรุปการรั่วไหลของหน่วยความจำ Android
วัตถุประสงค์ของการจัดการหน่วยความจำคือการช่วยให้เราหลีกเลี่ยงการรั่วไหลของหน่วยความจำในแอปพลิเคชันของเราอย่างมีประสิทธิภาพในระหว่างการพัฒนา ทุกคนคุ้นเคยกับการรั่วไหลของหน่วยความจำ เพื่อกล่าวอย่างง่ายดายนั่นหมายความว่าวัตถุที่ควรปล่อยออกมานั้นยังไม่ได้รับการปล่อยตัวและได้รับการจัดเก็บโดยอินสแตนซ์บางอย่าง แต่ไม่ได้ใช้อีกต่อไปเพื่อไม่ให้ GC ไม่สามารถรีไซเคิลได้ เมื่อเร็ว ๆ นี้ฉันได้อ่านเอกสารและวัสดุที่เกี่ยวข้องมากมาย ฉันวางแผนที่จะสรุปและชำระพวกเขาและแบ่งปันและเรียนรู้กับคุณและยังให้คำเตือนกับตัวเองเกี่ยวกับวิธีหลีกเลี่ยงสถานการณ์เหล่านี้ในระหว่างการเขียนโค้ดในอนาคตและปรับปรุงประสบการณ์และคุณภาพของแอปพลิเคชัน
ฉันจะเริ่มต้นด้วยพื้นฐานของการรั่วไหลของหน่วยความจำ Java และใช้ตัวอย่างเฉพาะเพื่อแสดงให้เห็นถึงสาเหตุต่าง ๆ ของการรั่วไหลของหน่วยความจำของ Android รวมถึงวิธีการใช้เครื่องมือในการวิเคราะห์การรั่วไหลของหน่วยความจำแอปพลิเคชันและสรุปได้ในที่สุด
กลยุทธ์การจัดสรรหน่วยความจำ Java
มีกลยุทธ์การจัดสรรหน่วยความจำสามประเภทเมื่อโปรแกรม Java ทำงานคือการจัดสรรแบบคงที่การจัดสรรสแต็กและการจัดสรรกอง ตามลําดับพื้นที่หน่วยความจำที่ใช้โดยกลยุทธ์การจัดเก็บทั้งสามส่วนใหญ่เป็นพื้นที่เก็บข้อมูลแบบคงที่ (หรือที่เรียกว่าพื้นที่วิธีการ) พื้นที่สแต็กและพื้นที่กอง
พื้นที่เก็บข้อมูลแบบคงที่ (พื้นที่วิธีการ): ส่วนใหญ่จัดเก็บข้อมูลคงที่ข้อมูลคงที่และค่าคงที่ทั่วโลก หน่วยความจำชิ้นนี้ได้รับการจัดสรรเมื่อโปรแกรมถูกรวบรวมและมีอยู่ตลอดการรันโปรแกรม
พื้นที่สแต็ค: เมื่อมีการดำเนินการวิธีการตัวแปรท้องถิ่นในตัวถังวิธี (รวมถึงชนิดข้อมูลพื้นฐานและการอ้างอิงวัตถุ) จะถูกสร้างขึ้นบนสแต็กและหน่วยความจำที่จัดขึ้นโดยตัวแปรท้องถิ่นเหล่านี้จะถูกปล่อยออกมาโดยอัตโนมัติเมื่อสิ้นสุดการดำเนินการวิธีการ เนื่องจากการดำเนินการจัดสรรหน่วยความจำสแต็กถูกสร้างขึ้นในชุดคำสั่งของโปรเซสเซอร์จึงมีประสิทธิภาพมาก แต่ความจุหน่วยความจำที่จัดสรรนั้นมี จำกัด
พื้นที่ฮีป: หรือที่เรียกว่าการจัดสรรหน่วยความจำแบบไดนามิกมักจะหมายถึงหน่วยความจำที่ใหม่โดยตรงเมื่อโปรแกรมทำงานอยู่นั่นคืออินสแตนซ์ของวัตถุ เมื่อส่วนหนึ่งของหน่วยความจำไม่ได้ใช้งานตัวเก็บขยะ Java จะต้องรับผิดชอบในการรีไซเคิล
ความแตกต่างระหว่างสแต็คและกอง:
ตัวแปรพื้นฐานบางประเภทที่กำหนดไว้ในตัวแปรวิธีการและตัวแปรอ้างอิงของวัตถุได้รับการจัดสรรในหน่วยความจำสแต็กของวิธีการ เมื่อตัวแปรถูกกำหนดไว้ในบล็อกของวิธี Java จะจัดสรรพื้นที่หน่วยความจำสำหรับตัวแปรบนสแต็ก เมื่อขอบเขตของตัวแปรเกินกว่าตัวแปรจะไม่ถูกต้องและพื้นที่หน่วยความจำที่จัดสรรให้กับมันจะเป็นอิสระและพื้นที่หน่วยความจำสามารถนำกลับมาใช้ใหม่ได้
หน่วยความจำฮีปใช้เพื่อจัดเก็บวัตถุทั้งหมดที่สร้างโดยใหม่ (รวมถึงตัวแปรสมาชิกทั้งหมดในวัตถุ) และอาร์เรย์ หน่วยความจำที่จัดสรรในกองจะได้รับการจัดการโดยอัตโนมัติโดยนักสะสมขยะ Java หลังจากอาร์เรย์หรือวัตถุถูกสร้างขึ้นในฮีปตัวแปรพิเศษสามารถกำหนดได้ในสแต็ก ค่าของตัวแปรนี้เท่ากับที่อยู่แรกของอาร์เรย์หรือวัตถุในหน่วยความจำฮีป ตัวแปรพิเศษนี้เป็นตัวแปรอ้างอิงที่เรากล่าวถึงข้างต้น เราสามารถเข้าถึงวัตถุหรืออาร์เรย์ในกองผ่านตัวแปรอ้างอิงนี้
ตัวอย่างเช่น:
ตัวอย่างคลาสสาธารณะ {int s1 = 0; ตัวอย่าง msample1 = ตัวอย่างใหม่ (); วิธีโมฆะสาธารณะ () {int s2 = 1; ตัวอย่าง msample2 = ตัวอย่างใหม่ ();}} ตัวอย่าง msample3 = ตัวอย่างใหม่ (); ตัวแปรท้องถิ่น S2 ของคลาสตัวอย่างและตัวแปรอ้างอิง MSAMPLE2 ทั้งคู่มีอยู่บนสแต็ก แต่วัตถุที่ชี้ไปที่ MSAMPLE2 มีอยู่บนกอง
เอนทิตีวัตถุที่ชี้ไปที่ MSAMPLE3 ถูกเก็บไว้ในกองรวมถึงตัวแปรสมาชิกทั้งหมด S1 และ MSAMPLE1 ของวัตถุนี้และมีอยู่ในสแต็ก
สรุปแล้ว:
ชนิดข้อมูลพื้นฐานและการอ้างอิงของตัวแปรท้องถิ่นจะถูกเก็บไว้ในสแต็กและหน่วยงานอ้างอิงจะถูกเก็บไว้ในกอง - เนื่องจากเป็นของตัวแปรในวิธีการวงจรชีวิตจะจบลงด้วยวิธีการ
ตัวแปรสมาชิกทั้งหมดจะถูกเก็บไว้และในฮีป (รวมถึงประเภทข้อมูลพื้นฐานการอ้างอิงและเอนทิตีวัตถุอ้างอิง) - เนื่องจากเป็นของคลาสวัตถุคลาสจะถูกใช้ในที่สุดสำหรับการใช้งานใหม่
หลังจากทำความเข้าใจการจัดสรรความทรงจำของ Java ลองมาดูกันว่า Java จัดการหน่วยความจำได้อย่างไร
Java จัดการความทรงจำได้อย่างไร
การจัดการหน่วยความจำของ Java เป็นปัญหาของการจัดสรรวัตถุและการเปิดตัว ใน Java โปรแกรมเมอร์จำเป็นต้องใช้สำหรับพื้นที่หน่วยความจำสำหรับแต่ละวัตถุผ่านคำหลักใหม่ (ยกเว้นประเภทพื้นฐาน) และวัตถุทั้งหมดจัดสรรพื้นที่ในกอง (heap) นอกจากนี้การปล่อยวัตถุจะถูกกำหนดและดำเนินการโดย GC ใน Java การจัดสรรหน่วยความจำทำได้โดยโปรแกรมในขณะที่การเปิดตัวหน่วยความจำจะทำโดย GC วิธีการของรายได้และค่าใช้จ่ายในสองบรรทัดนี้ทำให้การทำงานของโปรแกรมเมอร์ง่ายขึ้น แต่ในเวลาเดียวกันก็ยังเพิ่มการทำงานของ JVM นี่เป็นหนึ่งในเหตุผลที่โปรแกรม Java ทำงานช้าลง เนื่องจากเพื่อให้ปล่อยวัตถุอย่างถูกต้อง GC จะต้องตรวจสอบสถานะการทำงานของแต่ละวัตถุรวมถึงแอปพลิเคชันการอ้างอิงการอ้างอิงการมอบหมาย ฯลฯ ของวัตถุและ GC จำเป็นต้องตรวจสอบ
การตรวจสอบสถานะของวัตถุคือการปล่อยวัตถุอย่างแม่นยำมากขึ้นและในเวลาที่เหมาะสมและหลักการพื้นฐานของการปล่อยวัตถุคือวัตถุนั้นไม่ได้อ้างอิงอีกต่อไป
เพื่อให้เข้าใจได้ดีขึ้นว่า GC ทำงานอย่างไรเราสามารถพิจารณาวัตถุเป็นจุดสุดยอดของกราฟกำกับและความสัมพันธ์อ้างอิงเป็นขอบกำกับของกราฟซึ่งชี้จากผู้อ้างอิงไปยังวัตถุอ้างอิง นอกจากนี้แต่ละวัตถุเธรดสามารถใช้เป็นจุดสุดยอดเริ่มต้นของกราฟ ตัวอย่างเช่นโปรแกรมส่วนใหญ่เริ่มต้นจากกระบวนการหลักดังนั้นกราฟจึงเป็นทรีรูทเริ่มต้นด้วยจุดสุดยอดกระบวนการหลัก ในกราฟกำกับนี้วัตถุที่สามารถเข้าถึงได้โดยรูทจุดสุดยอดเป็นวัตถุที่ถูกต้องและ GC จะไม่รีไซเคิลวัตถุเหล่านี้ หากวัตถุ (subgraph ที่เชื่อมต่อ) ไม่สามารถเข้าถึงได้จากจุดสุดยอดรูทนี้ (โปรดทราบว่ากราฟเป็นกราฟกำกับ) เราเชื่อว่าวัตถุ (เหล่านั้น) นี้ไม่ได้อ้างอิงอีกต่อไปและสามารถรีไซเคิลได้โดย GC
ด้านล่างเราให้ตัวอย่างของวิธีใช้กราฟกำกับเพื่อแสดงการจัดการหน่วยความจำ สำหรับทุกช่วงเวลาของโปรแกรมเรามีกราฟกำกับที่แสดงถึงการจัดสรรหน่วยความจำของ JVM ภาพด้านล่างเป็นไดอะแกรมของโปรแกรมทางด้านซ้ายวิ่งไปยังบรรทัดที่ 6
Java ใช้กราฟโดยตรงสำหรับการจัดการหน่วยความจำซึ่งสามารถกำจัดปัญหาของลูปอ้างอิงได้ ตัวอย่างเช่นมีวัตถุสามชิ้นที่อ้างถึงซึ่งกันและกัน ตราบใดที่พวกเขาและกระบวนการรูทยังไม่สามารถเข้าถึงได้ GC ก็สามารถรีไซเคิลได้ ข้อดีของวิธีนี้คือมีความแม่นยำสูงในการจัดการหน่วยความจำ แต่มีประสิทธิภาพต่ำ เทคโนโลยีการจัดการหน่วยความจำที่ใช้กันทั่วไปอีกอย่างหนึ่งคือการใช้เคาน์เตอร์ ตัวอย่างเช่นโมเดล COM ใช้วิธีการนับเพื่อจัดการส่วนประกอบ เมื่อเทียบกับกราฟที่กำกับนั้นมีเส้นที่มีความแม่นยำต่ำ (เป็นเรื่องยากที่จะจัดการกับปัญหาการอ้างอิงแบบวงกลม) แต่มีประสิทธิภาพการดำเนินการสูง
หน่วยความจำรั่วในชวาคืออะไร
ใน Java การรั่วไหลของหน่วยความจำคือการมีอยู่ของวัตถุที่จัดสรรบางอย่างซึ่งมีสองลักษณะดังต่อไปนี้ ก่อนอื่นวัตถุเหล่านี้สามารถเข้าถึงได้นั่นคือในกราฟกำกับมีเส้นทางที่สามารถเชื่อมต่อกับพวกเขาได้ ประการที่สองวัตถุเหล่านี้ไร้ประโยชน์นั่นคือโปรแกรมจะไม่ใช้วัตถุเหล่านี้อีกในอนาคต หากวัตถุตรงกับเงื่อนไขทั้งสองนี้วัตถุเหล่านี้สามารถพิจารณาได้ว่าเป็นหน่วยความจำรั่วไหลใน Java และวัตถุเหล่านี้จะไม่ถูกรีไซเคิลโดย GC แต่มันใช้หน่วยความจำ
ใน C ++ การรั่วไหลของหน่วยความจำมีช่วงที่ใหญ่กว่า วัตถุบางอย่างได้รับการจัดสรรพื้นที่หน่วยความจำ แต่ไม่สามารถเข้าถึงได้ เนื่องจากไม่มี GC ใน C ++ หน่วยความจำเหล่านี้จะไม่ถูกรวบรวม ใน Java วัตถุที่ไม่สามารถเข้าถึงได้เหล่านี้ถูกนำกลับมาใช้ใหม่โดย GC ดังนั้นโปรแกรมเมอร์ไม่จำเป็นต้องพิจารณาการรั่วไหลของหน่วยความจำส่วนนี้
จากการวิเคราะห์เรารู้ว่าสำหรับ C ++ โปรแกรมเมอร์จำเป็นต้องจัดการขอบและจุดยอดด้วยตัวเองในขณะที่สำหรับโปรแกรมเมอร์ Java พวกเขาจำเป็นต้องจัดการขอบเท่านั้น (ไม่จำเป็นต้องจัดการการเปิดตัวของจุดยอด) ด้วยวิธีนี้ Java ปรับปรุงประสิทธิภาพการเขียนโปรแกรม
ดังนั้นจากการวิเคราะห์ข้างต้นเรารู้ว่ามีการรั่วไหลของหน่วยความจำใน Java แต่ขอบเขตมีขนาดเล็กกว่าของ C ++ เนื่องจากภาษา Java รับประกันได้ว่าวัตถุใด ๆ ที่สามารถเข้าถึงได้วัตถุที่ไม่สามารถเข้าถึงได้ทั้งหมดได้รับการจัดการโดย GC
สำหรับโปรแกรมเมอร์ GC นั้นมีความโปร่งใสและมองไม่เห็น แม้ว่าเราจะมีเพียงไม่กี่ฟังก์ชั่นในการเข้าถึง GC เช่น System.gc () ซึ่งเรียกใช้ GC ตามคำจำกัดความของข้อกำหนดภาษา Java ฟังก์ชั่นนี้ไม่รับประกันว่าตัวเก็บขยะของ JVM จะดำเนินการ เนื่องจากผู้ใช้ JVM ที่แตกต่างกันอาจใช้อัลกอริทึมที่แตกต่างกันเพื่อจัดการ GC โดยทั่วไปเธรดของ GC มีลำดับความสำคัญต่ำกว่า มีกลยุทธ์มากมายสำหรับ JVM ที่จะโทรหา GC บางคนเริ่มทำงานเฉพาะเมื่อการใช้หน่วยความจำถึงระดับหนึ่ง บางคนดำเนินการเป็นประจำ บางคนดำเนินการ GC อย่างราบรื่นและบางคนดำเนินการ GC ในลักษณะที่ขัดจังหวะ แต่โดยทั่วไปแล้วเราไม่จำเป็นต้องสนใจเรื่องนี้ เว้นแต่ในบางสถานการณ์การดำเนินการของ GC มีผลต่อประสิทธิภาพของแอปพลิเคชัน ตัวอย่างเช่นสำหรับระบบบนเว็บแบบเรียลไทม์เช่นเกมออนไลน์ผู้ใช้ไม่ต้องการให้ GC ขัดจังหวะการดำเนินการของแอปพลิเคชันและดำเนินการรวบรวมขยะจากนั้นเราจำเป็นต้องปรับพารามิเตอร์ของ GC เพื่อให้ GC สามารถใช้หน่วยความจำได้อย่างราบรื่น ฮอตสปอต JVM จัดทำโดย Sun รองรับคุณสมบัตินี้
ยังให้ตัวอย่างทั่วไปของการรั่วไหลของหน่วยความจำ Java
เวกเตอร์ v = เวกเตอร์ใหม่ (10); สำหรับ (int i = 1; i <100; i ++) {วัตถุ o = วัตถุใหม่ (); v.add (o); o = null; -ในตัวอย่างนี้เราใช้สำหรับวัฏจักรวัตถุวัตถุและใส่วัตถุที่ใช้ในเวกเตอร์ หากเราปล่อยการอ้างอิงเท่านั้นเวกเตอร์ยังคงอ้างอิงวัตถุดังนั้นวัตถุนี้ไม่สามารถรีไซเคิลได้สำหรับ GC ดังนั้นหากวัตถุจะต้องถูกลบออกจากเวกเตอร์หลังจากเพิ่มเข้าไปในเวกเตอร์วิธีที่ง่ายที่สุดคือการตั้งค่าวัตถุเวกเตอร์เป็นโมฆะ
หน่วยความจำรั่วใน Java โดยละเอียด
1. กลไกการรีไซเคิลหน่วยความจำ Java
โดยไม่คำนึงถึงวิธีการจัดสรรหน่วยความจำของภาษาใด ๆ จำเป็นต้องส่งคืนที่อยู่จริงของหน่วยความจำที่จัดสรรนั่นคือส่งคืนตัวชี้ไปยังที่อยู่แรกของบล็อกหน่วยความจำ วัตถุใน Java ถูกสร้างขึ้นโดยใช้วิธีการใหม่หรือการสะท้อนกลับ การสร้างวัตถุเหล่านี้จัดสรรในกอง วัตถุทั้งหมดจะถูกรวบรวมโดยเครื่องเสมือน Java ผ่านกลไกการรวบรวมขยะ เพื่อที่จะปล่อยวัตถุอย่างถูกต้อง GC จะตรวจสอบสถานะสุขภาพของแต่ละวัตถุและตรวจสอบแอปพลิเคชันการอ้างอิงการอ้างอิงการมอบหมาย ฯลฯ Java จะใช้วิธีการกราฟโดยตรงเพื่อจัดการหน่วยความจำเพื่อตรวจสอบว่าวัตถุสามารถทำได้แบบเรียลไทม์หรือไม่ หากยังไม่ถึงจะมีการรีไซเคิลซึ่งสามารถกำจัดปัญหาของลูปอ้างอิงได้ ในภาษา Java มีพื้นที่หน่วยความจำสองประเภทที่กำหนดว่าพื้นที่หน่วยความจำตรงตามเกณฑ์การรวบรวมขยะ: หนึ่งคือการกำหนดค่าว่างให้กับวัตถุซึ่งไม่ได้ถูกเรียกว่าด้านล่างและอีกอันคือการกำหนดค่าใหม่ให้กับวัตถุดังนั้นจึงจัดวางพื้นที่หน่วยความจำใหม่
2. สาเหตุของการรั่วไหลของหน่วยความจำ Java
การรั่วไหลของหน่วยความจำหมายถึงวัตถุที่ไร้ประโยชน์อย่างต่อเนื่อง (วัตถุที่ไม่ได้ใช้อีกต่อไป) หรือหน่วยความจำของวัตถุที่ไร้ประโยชน์ไม่สามารถปล่อยออกมาได้ในเวลาส่งผลให้เสียพื้นที่หน่วยความจำซึ่งเรียกว่าการรั่วไหลของหน่วยความจำ การรั่วไหลของหน่วยความจำบางครั้งไม่ร้ายแรงและไม่ง่ายที่จะตรวจจับดังนั้นนักพัฒนาไม่ทราบว่ามีการรั่วไหลของหน่วยความจำ แต่บางครั้งมันอาจร้ายแรงมากและจะกระตุ้นให้คุณออกจากหน่วยความจำ
สาเหตุของการรั่วไหลของหน่วยความจำ Java คืออะไร? หากวัตถุวัฏจักรชีวิตยาวนานมีการอ้างอิงถึงวัตถุวัฏจักรชีวิตระยะสั้นอาจเป็นไปได้ว่าการรั่วไหลของหน่วยความจำจะเกิดขึ้น แม้ว่าจะไม่จำเป็นต้องใช้วัตถุวัฏจักรชีวิตระยะสั้นอีกต่อไป แต่ก็ไม่สามารถรีไซเคิลได้เนื่องจากมีการอ้างอิงสำหรับวัฏจักรชีวิตที่ยาวนาน นี่คือสถานการณ์ที่หน่วยความจำรั่วเกิดขึ้นใน Java ส่วนใหญ่มีหมวดหมู่ต่อไปนี้:
1. คลาสคอลเลกชันแบบคงที่ทำให้หน่วยความจำรั่ว:
การใช้ HashMap, เวกเตอร์ ฯลฯ มีแนวโน้มที่จะเกิดขึ้นในการรั่วไหลของหน่วยความจำ วงจรชีวิตของตัวแปรคงที่เหล่านี้สอดคล้องกับแอปพลิเคชัน วัตถุทั้งหมดที่พวกเขาอ้างอิงไม่สามารถปล่อยออกมาได้เพราะมันจะถูกอ้างอิงโดยเวกเตอร์ ฯลฯ
ตัวอย่างเช่น
เวกเตอร์คงที่ v = เวกเตอร์ใหม่ (10); สำหรับ (int i = 1; i <100; i ++) {วัตถุ o = วัตถุใหม่ (); v.add (o); o = null;}ในตัวอย่างนี้วัตถุวัตถุถูกนำไปใช้ลูปและวัตถุที่ใช้จะถูกวางลงในเวกเตอร์ หากการอ้างอิงนั้นถูกปล่อยออกมาเท่านั้น (O = NULL) เวกเตอร์ยังคงอ้างอิงวัตถุดังนั้นวัตถุนี้ไม่สามารถรีไซเคิลได้สำหรับ GC ดังนั้นหากวัตถุจะต้องถูกลบออกจากเวกเตอร์หลังจากเพิ่มเข้าไปในเวกเตอร์วิธีที่ง่ายที่สุดคือการตั้งค่าวัตถุเวกเตอร์เป็นโมฆะ
2. เมื่อคุณสมบัติของวัตถุในคอลเลกชันได้รับการแก้ไขวิธีการลบ () จะไม่ทำงาน
ตัวอย่างเช่น:
โมฆะคงที่สาธารณะหลัก (String [] args) {set <person> set = new hashset <person> (); บุคคล p1 = บุคคลใหม่ ("Tang Monk", "Pwd1", 25); Person P2 = บุคคลใหม่ ("Sun Wukong", "Pwd2", 26) bajie "," pwd3 ", 27); set.add (p1); set.add (p2); set.add (p3); system.out.println (" มีทั้งหมด: "set.size ()+" องค์ประกอบ! "); // ผลลัพธ์: มีทั้งหมด: 3 องค์ประกอบ! p3.setage (2); // ปรับเปลี่ยนอายุของ P3 และค่า hashcode ที่สอดคล้องกับการเปลี่ยนแปลงองค์ประกอบ P3 ณ เวลานี้ set.remove (p3); // ลบออกในเวลานี้ทำให้เกิดการรั่วไหลของหน่วยความจำ Set.add (P3); // เพิ่มอีกครั้งและมันจะเพิ่ม System.out.println ได้สำเร็จ ("มี:"+set.size ()+"องค์ประกอบ!"); // ผลลัพธ์: มี: 4 องค์ประกอบทั้งหมด! สำหรับ (บุคคลบุคคล: set) {system.out.println (บุคคล);}}3. ผู้ฟัง
ในการเขียนโปรแกรม Java เราทุกคนต้องจัดการกับผู้ฟัง โดยปกติแล้วมีผู้ฟังจำนวนมากที่ใช้ในแอปพลิเคชัน เราจะเรียกวิธีการควบคุมเช่น addxxxListener () เพื่อเพิ่มผู้ฟัง แต่บ่อยครั้งเมื่อปล่อยวัตถุเราจำไม่ได้ว่าจะลบผู้ฟังเหล่านี้ซึ่งจะเป็นการเพิ่มโอกาสในการรั่วไหลของหน่วยความจำ
4. การเชื่อมต่อต่างๆ
ตัวอย่างเช่นการเชื่อมต่อฐานข้อมูล (DataSourse.getConnection ()) การเชื่อมต่อเครือข่าย (ซ็อกเก็ต) และการเชื่อมต่อ IO จะไม่ถูกนำกลับมาใช้ใหม่โดย GC โดยอัตโนมัติเว้นแต่จะเรียกวิธีการปิด () อย่างชัดเจนเพื่อปิดการเชื่อมต่อ วัตถุผลลัพธ์และคำสั่งไม่สามารถรีไซเคิลได้อย่างชัดเจน แต่การเชื่อมต่อจะต้องรีไซเคิลอย่างชัดเจนเนื่องจากการเชื่อมต่อไม่สามารถรีไซเคิลได้ตลอดเวลา เมื่อการเชื่อมต่อถูกรีไซเคิลวัตถุ ResultSet และคำสั่งจะเป็นโมฆะทันที อย่างไรก็ตามหากคุณใช้กลุ่มการเชื่อมต่อสถานการณ์จะแตกต่างกัน นอกเหนือจากการปิดการเชื่อมต่ออย่างชัดเจนคุณต้องปิดวัตถุคำสั่ง ResultSet อย่างชัดเจน (ปิดหนึ่งในนั้นอีกอันหนึ่งจะถูกปิดด้วย) มิฉะนั้นวัตถุคำสั่งจำนวนมากจะไม่ถูกปล่อยออกมาทำให้เกิดการรั่วไหลของหน่วยความจำ ในกรณีนี้การเชื่อมต่อมักจะถูกปล่อยออกมาในการลองและในที่สุด
5. การอ้างอิงถึงคลาสภายในและโมดูลภายนอก
การอ้างอิงถึงชั้นเรียนภายในนั้นค่อนข้างง่ายที่จะลืมและเมื่อไม่ได้รับการปล่อยตัวชุดวัตถุระดับผู้สืบทอดอาจไม่ได้รับการปล่อยตัว นอกจากนี้โปรแกรมเมอร์ควรระวังการอ้างอิงโดยไม่ตั้งใจไปยังโมดูลภายนอก ตัวอย่างเช่นโปรแกรมเมอร์ A รับผิดชอบโมดูล A และเรียกใช้วิธีการของโมดูล B เช่น:
โมฆะสาธารณะ registermsg (วัตถุ B);
การโทรแบบนี้ต้องใช้ความระมัดระวังเป็นอย่างมาก เมื่อวัตถุถูกส่งผ่านมันเป็นไปได้มากที่โมดูล B จะอ้างอิงถึงวัตถุ ในเวลานี้คุณต้องให้ความสนใจว่าโมดูล B ให้การดำเนินการที่สอดคล้องกันเพื่อลบการอ้างอิงหรือไม่
6. โหมดซิงเกิลตัน
การใช้รูปแบบซิงเกิลไม่ถูกต้องเป็นปัญหาที่พบบ่อยที่ทำให้เกิดการรั่วไหลของหน่วยความจำ วัตถุซิงเกิลตันจะมีอยู่ตลอดวงจรชีวิตทั้งหมดของ JVM หลังจากเริ่มต้น (ในรูปแบบของตัวแปรคงที่) หากวัตถุซิงเกิลมีการอ้างอิงภายนอกวัตถุนี้จะไม่ถูกนำกลับมาใช้ใหม่โดย JVM จะส่งผลให้หน่วยความจำรั่วไหล พิจารณาตัวอย่างต่อไปนี้:
คลาส A {สาธารณะ a () {b.getInstance (). seta (this);} .... } // class b ใช้คลาสโหมดซิงเกิลโหมด b {ส่วนตัว a a; อินสแตนซ์คงที่ส่วนตัว b () b (); b () {} public betinstance ()เห็นได้ชัดว่า B ใช้รูปแบบ Singleton ซึ่งมีการอ้างอิงถึงวัตถุ A และวัตถุของ Class A นี้จะไม่ถูกนำกลับมาใช้ใหม่ ลองนึกภาพว่าจะเกิดอะไรขึ้นถ้า A เป็นวัตถุหรือประเภทคอลเลกชันที่ซับซ้อนมากขึ้น
สรุปการรั่วไหลของหน่วยความจำทั่วไปใน Android
การรั่วไหลของคลาสคอลเลกชัน
หากคลาสคอลเลกชันมีวิธีการเพิ่มองค์ประกอบและไม่มีกลไกการลบที่สอดคล้องกันเท่านั้นหน่วยความจำจะถูกครอบครอง หากคลาสคอลเลกชันนี้เป็นตัวแปรระดับโลก (เช่นคุณสมบัติคงที่ในชั้นเรียนแผนที่ทั่วโลก ฯลฯ นั่นคือมีการอ้างอิงแบบคงที่หรือการชี้ไปที่สุดท้ายตลอดเวลา) จากนั้นไม่มีกลไกการลบที่สอดคล้องกันซึ่งอาจทำให้หน่วยความจำที่ถูกครอบครองโดยการรวบรวมเพื่อเพิ่มและไม่ลดลง ตัวอย่างเช่นตัวอย่างทั่วไปด้านบนเป็นหนึ่งในสถานการณ์เหล่านี้ แน่นอนว่าเราจะไม่เขียนรหัส 2B ดังกล่าวในโครงการ แต่ก็ยังง่ายที่จะเกิดขึ้นหากเราไม่ระวัง ตัวอย่างเช่นเราทุกคนชอบทำแคชผ่าน HashMap ดังนั้นเราควรระวังให้มากขึ้นในสถานการณ์นี้
การรั่วไหลของหน่วยความจำที่เกิดจาก singletons
เนื่องจากลักษณะคงที่ของซิงเกิลตันทำให้วัฏจักรชีวิตของมันตราบเท่าที่วงจรชีวิตของแอปพลิเคชันหากใช้อย่างไม่เหมาะสมจึงเป็นเรื่องง่ายที่จะทำให้เกิดการรั่วไหลของหน่วยความจำ ตัวอย่างเช่นตัวอย่างทั่วไปต่อไปนี้
AppManager คลาสสาธารณะ {อินสแตนซ์ appmanager ส่วนตัวแบบคงที่บริบทบริบทส่วนตัว; AppManager ส่วนตัว (บริบทบริบท) {this.context = บริบท;} สาธารณะ appManager สาธารณะ getInstance (บริบทบริบท) {ถ้า (อินสแตนซ์ == null) {อินสแตนซ์ = ใหม่ appManager (บริบท);}}}}}}}}}}}นี่เป็นรูปแบบ Singleton ปกติ เมื่อสร้างซิงเกิลนี้เนื่องจากบริบทจะต้องส่งผ่านความยาวของวงจรชีวิตของบริบทนี้จึงเป็นสิ่งสำคัญ:
1. หากบริบทของแอปพลิเคชันถูกส่งผ่านในเวลานี้เนื่องจากวงจรชีวิตของแอปพลิเคชันเป็นวัฏจักรชีวิตของแอปพลิเคชันทั้งหมดจะไม่มีปัญหา
2. หากบริบทกิจกรรมถูกส่งผ่านในเวลานี้เมื่อกิจกรรมที่สอดคล้องกับบริบทนี้ออกเนื่องจากการอ้างอิงถึงบริบทจะถูกจัดขึ้นโดยวัตถุซิงเกิลวงจรชีวิตของมันจะเท่ากับวงจรชีวิตแอปพลิเคชันทั้งหมดดังนั้นเมื่อกิจกรรมออกหน่วยความจำจะไม่ถูกรีไซเคิลซึ่งทำให้เกิดการรั่วไหล
วิธีที่ถูกต้องควรเปลี่ยนเป็นสิ่งต่อไปนี้:
AppManager คลาสสาธารณะ {อินสแตนซ์ appmanager ส่วนตัว; บริบทส่วนตัวบริบทส่วนตัว; AppManager ส่วนตัว (บริบทบริบท) {this.context = context.getApplicationContext (); // บริบทโดยใช้แอปพลิเคชัน} แอปพลิเคชันสาธารณะคงที่ getInstance (บริบทบริบท) {ถ้า (อินสแตนซ์ == null) {อินสแตนซ์ = ใหม่หรือเขียนด้วยวิธีนี้และคุณไม่จำเป็นต้องผ่านบริบทใน:
เพิ่มวิธีการคงที่ลงในแอปพลิเคชันของคุณ getContext () ส่งคืนบริบทของแอปพลิเคชัน
-
บริบท = getApplicationContext (); .../*** รับบริบททั่วโลก*@return ส่งคืนวัตถุบริบททั่วโลก*/บริบทคงที่สาธารณะ getContext () {return context;} คลาสสาธารณะ AppManager {อินสแตนซ์ appManager ส่วนตัว; บริบทส่วนตัว; appManager ();} return อินสแตนซ์;}}ชั้นเรียนภายในที่ไม่ระบุชื่อ/คลาสภายในที่ไม่คงที่และเธรดแบบอะซิงโครนัส
การรั่วไหลของหน่วยความจำที่เกิดจากการสร้างอินสแตนซ์แบบคงที่ในชั้นเรียนที่ไม่คงที่
บางครั้งเราอาจเริ่มกิจกรรมบ่อยครั้ง เพื่อหลีกเลี่ยงการสร้างแหล่งข้อมูลเดียวกันซ้ำ ๆ วิธีการเขียนนี้อาจเกิดขึ้น:
MainActivity ระดับสาธารณะขยาย AppCompatactivity {ส่วนตัว testresource mreesource = null; @Overrideprotected void onCreate (BUNDLE SAVEDINSTANCESTATE) {Super.oncreate (SavedInstancestate); setContentView testResource ();} // ... } คลาส testResource {// ... }}สิ่งนี้จะสร้างซิงเกิลของชั้นเรียนภายในที่ไม่คงที่ภายในกิจกรรมและข้อมูลของซิงเกิลถูกใช้ทุกครั้งที่เริ่มกิจกรรม แม้ว่าการสร้างทรัพยากรซ้ำ ๆ จะหลีกเลี่ยงการเขียนนี้จะทำให้เกิดการรั่วไหลของหน่วยความจำเนื่องจากคลาสภายในที่ไม่คงที่จะเก็บข้อมูลอ้างอิงไปยังคลาสภายนอกโดยค่าเริ่มต้นและชั้นเรียนภายในที่ไม่คงที่จะสร้างอินสแตนซ์แบบคงที่และวัฏจักรชีวิตของอินสแตนซ์ตราบเท่าที่แอปพลิเคชัน วิธีที่ถูกต้องในการทำคือ:
ตั้งค่าคลาสด้านในเป็นคลาสภายในแบบคงที่หรือแยกคลาสด้านในและห่อหุ้มมันลงในซิงเกิล หากคุณต้องการใช้บริบทโปรดติดตามบริบทที่แนะนำข้างต้นเพื่อใช้แอปพลิเคชัน แน่นอนบริบทของแอปพลิเคชันไม่ได้มีอำนาจทุกอย่างดังนั้นจึงไม่สามารถใช้แบบสุ่มได้ ในบางสถานที่คุณต้องใช้บริบทของกิจกรรม สถานการณ์แอปพลิเคชันของบริบทของแอปพลิเคชันบริการและกิจกรรมมีดังนี้:
โดยที่: NO1 หมายความว่าแอปพลิเคชันและบริการสามารถเริ่มกิจกรรมได้ แต่ต้องสร้างคิวงานใหม่ สำหรับบทสนทนาสามารถสร้างขึ้นได้ในกิจกรรมเท่านั้น
ชั้นเรียนภายในที่ไม่ระบุชื่อ
การพัฒนา Android มักสืบทอดการดำเนินการตามกิจกรรม/ส่วน/มุมมอง ในเวลานี้หากคุณใช้คลาสที่ไม่ระบุชื่อและจัดขึ้นโดยเธรดแบบอะซิงโครนัสให้ระวัง หากไม่มีมาตรการมันจะนำไปสู่การรั่วไหลอย่างแน่นอน
คลาสสาธารณะ mainactivity ขยายกิจกรรม {... runnable ref1 = new myrunable (); runnable ref2 = new runnable () {@overridepublic void run () {}}; ... }ความแตกต่างระหว่าง ref1 และ ref2 คือ ref2 ใช้คลาสภายในที่ไม่ระบุชื่อ มาดูหน่วยความจำที่อ้างอิงเมื่อรันไทม์:
อย่างที่คุณเห็น ref1 ไม่มีอะไรพิเศษ
แต่มีการอ้างอิงเพิ่มเติมในวัตถุการนำไปใช้ของคลาสที่ไม่ระบุชื่อ REF2:
จุดอ้างอิง $ 0 นี้ไปยัง mainactivity นี่คืออินสแตนซ์หลักในปัจจุบันจะจัดขึ้นโดย ref2 หากการอ้างอิงนี้ถูกส่งผ่านไปยังเธรดแบบอะซิงโครนัสและเธรดนี้และวงจรชีวิตกิจกรรมนี้ไม่สอดคล้องกันกิจกรรมการรั่วไหลจะเกิดขึ้น
การรั่วไหลของหน่วยความจำที่เกิดจากตัวจัดการ
ปัญหาการรั่วไหลของหน่วยความจำที่เกิดจากการใช้ตัวจัดการควรกล่าวว่าเป็นเรื่องธรรมดาที่สุด เพื่อหลีกเลี่ยง ANR เราจะไม่ดำเนินการใช้เวลานานในเธรดหลักและใช้ตัวจัดการเพื่อจัดการงานเครือข่ายหรือห่อหุ้มการเรียกร้องการเรียกและ API อื่น ๆ อย่างไรก็ตามตัวจัดการไม่ได้มีอำนาจทุกอย่าง หากรหัสของตัวจัดการถูกเขียนในลักษณะที่เป็นมาตรฐานอาจทำให้หน่วยความจำรั่วไหล นอกจากนี้เรารู้ว่าตัวจัดการข้อความและข้อความนั้นเกี่ยวข้องกันทั้งหมด ในกรณีที่ข้อความที่ส่งโดย Handler ยังไม่ได้รับการประมวลผลข้อความและวัตถุ Handler ที่ส่งมันจะถูกเก็บไว้โดย Thread MessageQueue
เนื่องจากตัวจัดการเป็นของตัวแปร TLS (Thread Local Storage) วงจรชีวิตและกิจกรรมไม่สอดคล้องกัน ดังนั้นวิธีการใช้งานนี้โดยทั่วไปจึงเป็นเรื่องยากที่จะตรวจสอบให้แน่ใจว่าสอดคล้องกับวงจรชีวิตของมุมมองหรือกิจกรรมดังนั้นจึงเป็นเรื่องง่ายที่จะทำให้เกิดการปล่อยที่ถูกต้อง
ตัวอย่างเช่น:
ระดับสาธารณะ sampleActivity ขยายกิจกรรม {ส่วนตัว handler สุดท้าย mLeakyHandler = handler ใหม่ () {@Overridepublic ถือเป็นโมฆะ handleMessage (ข้อความข้อความ) {// ... }}@overrideprotected void onCreate (Bundle SavedInstancestate) minutes.mleakyhandler.postdelayed (ใหม่ runnable () {@overridepublic void run () {/ * ... */}}, 1,000 * 60 * 10); // กลับไปที่กิจกรรมก่อนหน้า finish ();}}}ข้อความล่าช้าการดำเนินการล่าช้า 10 นาทีจะถูกประกาศใน sampleActivity และ mleakyhandler ผลักมันลงในข้อความคิวข้อความ เมื่อกิจกรรมลดลงอย่างสมบูรณ์ () ข้อความที่ทำให้การดำเนินงานล่าช้าจะยังคงมีอยู่ในเธรดหลักซึ่งถือการอ้างอิงตัวจัดการของกิจกรรมดังนั้นกิจกรรมจะลดลงด้วยการเสร็จสิ้น () จะไม่ถูกนำไปรีไซเคิลทำให้เกิดการรั่วไหลของหน่วยความจำ
แก้ไข: หลีกเลี่ยงการใช้คลาสภายในที่ไม่คงที่ในกิจกรรม ตัวอย่างเช่นหากเราประกาศว่าผู้ดูแลเป็นแบบคงที่ด้านบนระยะเวลาการอยู่รอดของมันไม่มีส่วนเกี่ยวข้องกับวงจรชีวิตของกิจกรรม ในขณะเดียวกันกิจกรรมจะถูกนำมาใช้ผ่านการอ้างอิงที่อ่อนแอเพื่อหลีกเลี่ยงการส่งผ่านกิจกรรมโดยตรงเป็นบริบท ดูรหัสต่อไปนี้:
SampleActivity ระดับสาธารณะขยายกิจกรรม {/*** อินสแตนซ์ของคลาสชั้นในแบบคงที่ไม่ได้มีการอ้างอิงโดยนัย*การอ้างอิงถึงชั้นนอกของพวกเขา*/คลาสคงที่ส่วนตัว myhandler ขยายตัวจัดการ {private finalference finalference handleMessage (ข้อความข้อความ) {กิจกรรม sampleActivity = mActivity.get (); ถ้า (กิจกรรม! = null) {// ... }}} ส่วนตัว myhandler สุดท้าย MyHandler mhandler = new MyHandler (นี่); {@overridepublic เป็นโมฆะ Run () {/ * ... */}};@overrideprotected void onCreate (Bundle SavedInstancestate) {super.oncreate (savedinstancestate); // โพสต์ข้อความและความล่าช้า Activity.finish ();}}ภาพรวมขอแนะนำให้ใช้คลาสชั้นในแบบคงที่ + Weakreference ระวังให้ว่างเปล่าก่อนการใช้งานแต่ละครั้ง
การอ้างอิงที่อ่อนแอถูกกล่าวถึงก่อนหน้านี้ดังนั้นที่นี่ฉันจะพูดคุยสั้น ๆ เกี่ยวกับวัตถุอ้างอิงหลายประเภทของ Java
Java มีการอ้างอิงสี่ประเภท: การอ้างอิงที่แข็งแกร่ง, softreference, weakreference และ phatomreference
ในการพัฒนาแอพพลิเคชั่น Android เพื่อป้องกันไม่ให้หน่วยความจำล้นเมื่อจัดการกับวัตถุบางอย่างที่ครอบครองหน่วยความจำขนาดใหญ่และมีวงจรการประกาศที่ยาวนานการอ้างอิงที่อ่อนนุ่มและเทคโนโลยีอ้างอิงที่อ่อนแอสามารถใช้ได้มากที่สุด
การอ้างอิงที่อ่อนนุ่ม/อ่อนแอสามารถใช้ร่วมกับคิวอ้างอิง (อ้างอิง) หากวัตถุที่อ้างอิงโดยการอ้างอิงที่อ่อนนุ่มจะถูกนำกลับมาใช้ใหม่โดยเครื่องเก็บขยะ Garbage เครื่องเสมือน Java จะเพิ่มการอ้างอิงที่อ่อนนุ่มในคิวอ้างอิงที่เกี่ยวข้อง คิวนี้ช่วยให้คุณรู้รายการรีไซเคิลของการอ้างอิงที่อ่อนนุ่ม/อ่อนแอดังนั้นการล้างบัฟเฟอร์ที่ล้มเหลวในการอ้างอิงที่อ่อนนุ่ม/อ่อนแอ
สมมติว่าแอปพลิเคชันของเราจะใช้ภาพเริ่มต้นจำนวนมากเช่นอวตารเริ่มต้นไอคอนเกมเริ่มต้น ฯลฯ ซึ่งจะใช้ในหลาย ๆ ที่ หากคุณอ่านรูปภาพทุกครั้งมันจะช้าลงเนื่องจากการอ่านไฟล์ต้องใช้การทำงานของฮาร์ดแวร์ซึ่งจะนำไปสู่ประสิทธิภาพที่ต่ำกว่า ดังนั้นเราจึงพิจารณาแคชภาพและอ่านโดยตรงจากหน่วยความจำเมื่อจำเป็น อย่างไรก็ตามเนื่องจากภาพใช้พื้นที่หน่วยความจำจำนวนมากและแคชรูปภาพจำนวนมากต้องใช้หน่วยความจำจำนวนมากข้อยกเว้นที่ไม่ได้รับการยกเว้นอาจเกิดขึ้นได้ ในเวลานี้เราสามารถพิจารณาใช้เทคนิคการอ้างอิงที่อ่อนนุ่ม/อ่อนแอเพื่อหลีกเลี่ยงปัญหานี้ ต่อไปนี้เป็นต้นแบบของแคช:
ก่อนกำหนด HASHMAP และบันทึกวัตถุอ้างอิงซอฟต์
แผนที่ส่วนตัว <สตริง, softreference <bitmap>> imagecache = ใหม่ hashmap <สตริง, softreference <bitmap>> ();
มากำหนดวิธีการเพื่อบันทึกการอ้างอิงที่อ่อนนุ่มของบิตแมปไปยัง HashMap
หลังจากใช้การอ้างอิงที่อ่อนนุ่มก่อนที่จะมีข้อยกเว้น outofmemory เกิดขึ้นพื้นที่หน่วยความจำของทรัพยากรภาพที่แคชเหล่านี้สามารถเป็นอิสระได้ดังนั้นจึงป้องกันไม่ให้หน่วยความจำถึงขีด จำกัด สูงสุดและหลีกเลี่ยงการชน
หากคุณเพียงต้องการหลีกเลี่ยงการเกิดข้อยกเว้นที่เกิดจากการไม่ใช้งานคุณสามารถใช้การอ้างอิงที่อ่อนนุ่ม หากคุณใส่ใจเพิ่มเติมเกี่ยวกับประสิทธิภาพของแอปพลิเคชันของคุณและต้องการรีไซเคิลวัตถุบางอย่างที่ครอบครองหน่วยความจำมากขึ้นโดยเร็วที่สุดคุณสามารถใช้การอ้างอิงที่อ่อนแอ
นอกจากนี้คุณสามารถตรวจสอบได้ว่าวัตถุถูกใช้บ่อยเพื่อตรวจสอบว่ามีการเลือกสำหรับการอ้างอิงที่อ่อนนุ่มหรือการอ้างอิงที่อ่อนแอหรือไม่ หากวัตถุอาจใช้บ่อยลองใช้การอ้างอิงที่อ่อนนุ่ม หากวัตถุไม่ได้ใช้โอกาสมากขึ้นก็สามารถใช้กับการอ้างอิงที่อ่อนแอได้
ตกลงกลับไปที่หัวข้อต่อไป ดังที่ได้กล่าวไว้ก่อนหน้านี้สร้างคลาสภายในตัวจัดการแบบคงที่และใช้การอ้างอิงที่อ่อนแอไปยังวัตถุที่จัดขึ้นโดยตัวจัดการเพื่อให้วัตถุที่จับโดยตัวจัดการสามารถรีไซเคิลได้ในระหว่างการรีไซเคิล อย่างไรก็ตามแม้ว่าสิ่งนี้จะหลีกเลี่ยงการรั่วไหลของกิจกรรม แต่ก็ยังอาจมีข้อความรออยู่ในคิวข้อความของเธรด looper ดังนั้นเราควรลบข้อความในข้อความคิวข้อความในระหว่างการทำลายหรือหยุดกิจกรรม
วิธีการต่อไปนี้สามารถลบข้อความ:
Public Void สุดท้าย RemoveCallbacks (Runnable R); Public Void RemoveCallbacks (Runnable R, Object Token); โมฆะสุดท้ายสาธารณะ RemoveCallbackSandMessages (Object Token); โมฆะสุดท้ายสาธารณะ removeMessages (int What); public void สุดท้าย removeMessages (int What, Object Object);
พยายามหลีกเลี่ยงการใช้ตัวแปรสมาชิกแบบคงที่
หากตัวแปรสมาชิกถูกประกาศแบบคงที่เราทุกคนรู้ว่าวงจรชีวิตของมันจะเหมือนกับวงจรชีวิตของแอพทั้งหมด
สิ่งนี้จะทำให้เกิดปัญหา หากกระบวนการแอพของคุณได้รับการออกแบบให้เป็นผู้มีส่วนร่วมในหน่วยความจำแม้ว่าแอพจะตัดเป็นพื้นหลังส่วนหนึ่งของหน่วยความจำจะไม่ถูกปล่อยออกมา ตามกลไกการจัดการหน่วยความจำปัจจุบันของแอพมือถือกระบวนการพื้นหลังที่บัญชีสำหรับหน่วยความจำจำนวนมากจะถูกนำกลับมาใช้ใหม่ก่อน หากแอพนี้ได้ทำการป้องกันกระบวนการร่วมกันมันจะทำให้แอปเริ่มต้นใหม่บ่อยครั้งในพื้นหลัง เมื่อโทรศัพท์ติดตั้งแอพที่คุณเข้าร่วมในการพัฒนาโทรศัพท์จะใช้พลังงานและการจราจรข้ามคืนและแอพของคุณจะต้องถอนการติดตั้งหรือเงียบโดยผู้ใช้
การแก้ไขที่นี่คือ:
อย่าเริ่มต้นสมาชิกคงที่ที่จุดเริ่มต้นของชั้นเรียน การเริ่มต้นขี้เกียจสามารถพิจารณาได้
ในการออกแบบสถาปัตยกรรมเราควรคิดว่าจำเป็นต้องทำเช่นนี้และพยายามหลีกเลี่ยงหรือไม่ หากสถาปัตยกรรมจำเป็นต้องได้รับการออกแบบเช่นนี้คุณจะมีความรับผิดชอบในการจัดการวงจรชีวิตของวัตถุนี้
หลีกเลี่ยงการแทนที่เสร็จสิ้น ()
1. วิธีการสุดท้ายจะดำเนินการในเวลาที่ไม่แน่นอนและไม่สามารถพึ่งพาเพื่อปล่อยทรัพยากรที่หายาก เหตุผลของเวลาที่ไม่แน่นอนคือ:
เวลาที่เครื่องเสมือนเรียก GC ไม่แน่นอน
เวลาที่การกำหนดเธรด daemon finalize นั้นไม่แน่นอน
2. วิธีการสุดท้ายจะถูกดำเนินการเพียงครั้งเดียว แม้ว่าวัตถุจะถูกฟื้นคืนชีพหากวิธีการสุดท้ายได้ถูกดำเนินการแล้วมันจะไม่ถูกดำเนินการอีกครั้งเมื่อเป็น GC อีกครั้ง เหตุผลคือ:
วัตถุที่มีวิธีการสุดท้ายจะสร้างการอ้างอิงขั้นสุดท้ายโดยเครื่องเสมือนเมื่อใหม่และการอ้างอิงไปยังวัตถุ เมื่อมีการดำเนินการวิธีการสุดท้ายการอ้างอิงสุดท้ายที่สอดคล้องกับวัตถุจะถูกปล่อยออกมา แม้ว่าวัตถุจะฟื้นคืนชีพในเวลานี้ (นั่นคือการอ้างอิงวัตถุที่มีการอ้างอิงที่แข็งแกร่ง) และครั้งที่สองที่เป็น GC เนื่องจากการอ้างอิงสุดท้ายไม่สอดคล้องกับมันอีกต่อไปวิธีการสรุปจะไม่ถูกดำเนินการ
3. วัตถุที่มีวิธีการสรุปต้องผ่าน GC อย่างน้อยสองรอบก่อนที่จะสามารถปล่อยออกมาได้
การรั่วไหลของหน่วยความจำที่เกิดจากทรัพยากรที่ไม่ได้เปิด
对于使用了BraodcastReceiver,ContentObserver,File,游标Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。
一些不良代码造成的内存压力
有些代码并不造成内存泄露,但是它们,或是对没使用的内存没进行有效及时的释放,或是没有有效的利用已有的对象而是频繁的申请新内存。
ตัวอย่างเช่น:
Bitmap 没调用recycle()方法,对于Bitmap 对象在不使用时,我们应该先调用recycle() 释放内存,然后才它设置为null. 因为加载Bitmap 对象的内存空间,一部分是java 的,一部分C 的(因为Bitmap 分配的底层是通过JNI 调用的)。 而这个recyle() 就是针对C 部分的内存释放。
构造Adapter 时,没有使用缓存的convertView ,每次都在创建新的converView。这里推荐使用ViewHolder。
สรุป
对Activity 等组件的引用应该控制在Activity 的生命周期之内; 如果不能就考虑使用getApplicationContext 或者getApplication,以避免Activity 被外部长生命周期的对象引用而泄露。
尽量不要在静态变量或者静态内部类中使用非静态外部成员变量(包括context ),即使要使用,也要考虑适时把外部成员变量置空;也可以在内部类中使用弱引用来引用外部类的变量。
对于生命周期比Activity长的内部类对象,并且内部类中使用了外部类的成员变量,可以这样做避免内存泄漏:
将内部类改为静态内部类
静态内部类中使用弱引用来引用外部类的成员变量
Handler 的持有的引用对象最好使用弱引用,资源释放时也可以清空Handler 里面的消息。比如在Activity onStop 或者onDestroy 的时候,取消掉该Handler 对象的Message和Runnable.
在Java 的实现过程中,也要考虑其对象释放,最好的方法是在不使用某对象时,显式地将此对象赋值为null,比如使用完Bitmap 后先调用recycle(),再赋为null,清空对图片等资源有直接引用或者间接引用的数组(使用array.clear() ; array = null)等,最好遵循谁创建谁释放的原则。
正确关闭资源,对于使用了BraodcastReceiver,ContentObserver,File,游标Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销。
保持对对象生命周期的敏感,特别注意单例、静态对象、全局性集合等的生命周期。
The above is a summary of the causes of memory leaks in Java introduced to you by the editor and how to avoid memory leaks (super detailed version). ฉันหวังว่ามันจะเป็นประโยชน์กับทุกคน หากคุณมีคำถามใด ๆ โปรดฝากข้อความถึงฉันและบรรณาธิการจะตอบกลับคุณทันเวลา ขอบคุณมากสำหรับการสนับสนุนเว็บไซต์ Wulin.com!