สวัสดี!

ฉันคิดว่าคุณคงไม่แปลกใจถ้าฉันบอกคุณว่าคอมพิวเตอร์ของคุณมีหน่วยความจำจำกัด :) แม้แต่ฮาร์ดไดรฟ์ — โดยทั่วไปจะมีขนาดใหญ่กว่าหน่วยความจำ RAM หลายเท่า — ก็สามารถบรรจุเกม รายการทีวี รายการโปรดของคุณได้เต็มความจุ และอื่น ๆ. เพื่อป้องกันไม่ให้สิ่งนี้เกิดขึ้น คุณต้องตรวจสอบสถานะปัจจุบันของหน่วยความจำและลบไฟล์ที่ไม่จำเป็นออกจากคอมพิวเตอร์ของคุณ การเขียนโปรแกรม Java เกี่ยวข้องกับสิ่งเหล่านี้อย่างไร ทุกอย่าง! ท้ายที่สุดแล้ว เมื่อเครื่อง Java สร้างวัตถุใด ๆ ก็จะจัดสรรหน่วยความจำสำหรับวัตถุนั้น

ในโปรแกรมขนาดใหญ่จริงๆ มีการสร้างอ็อบเจกต์นับหมื่นนับแสนอ็อบเจกต์ และแต่ละออบเจกต์มีหน่วยความจำของตัวเองจัดสรรไว้ให้ แต่คุณคิดว่าวัตถุเหล่านี้มีอยู่นานแค่ไหน? พวกเขา "มีชีวิตอยู่" ตลอดเวลาที่โปรแกรมของเราทำงานหรือไม่? ไม่แน่นอน แม้ว่าจะมีข้อดีทั้งหมดของออบเจกต์ Java แต่ก็ไม่ได้เป็นอมตะ :) ออบเจ็กต์มีวงจรชีวิตของมันเอง วันนี้เราจะหยุดพักจากการเขียนโค้ดสักหน่อยแล้วมาดูกระบวนการนี้กัน :) ยิ่งไปกว่านั้น ความเข้าใจของคุณเกี่ยวกับวิธีการทำงานของโปรแกรมและวิธีการจัดการทรัพยากรเป็นสิ่งสำคัญมาก ชีวิตของวัตถุเริ่มต้นเมื่อใด เหมือนคน - ตั้งแต่กำเนิดนั่นคือการสร้าง


Cat cat = new Cat(); // Here the lifecycle of our Cat object begins!

ขั้นแรก Java Virtual Machine จะจัดสรรหน่วยความจำตามจำนวนที่ต้องการเพื่อสร้างอ็อบเจกต์ จากนั้นจะสร้างการอ้างอิงถึงหน่วยความจำนั้น ในกรณีของเรา การอ้างอิงนั้นเรียกว่าcatดังนั้นเราจึงสามารถติดตามได้ จากนั้นตัวแปรทั้งหมดจะถูกเตรียมใช้งาน ตัวสร้างจะถูกเรียก และ — ta-da! — วัตถุที่เพิ่งสร้างเสร็จของเรากำลังมีชีวิตของมันเอง :)

อายุการใช้งานของวัตถุแตกต่างกันไป เราจึงไม่สามารถระบุตัวเลขที่แน่นอนได้ที่นี่ ไม่ว่าในกรณีใด ๆ มันอาศัยอยู่ในโปรแกรมและทำหน้าที่ของมันเป็นระยะเวลาหนึ่ง เพื่อให้แม่นยำ วัตถุจะ "มีชีวิต" ตราบใดที่มีการอ้างอิงถึงวัตถุนั้น ทันทีที่ไม่มีการอ้างอิงเหลือ วัตถุ "ตาย" ตัวอย่าง:


public class Car {
  
   String model;

   public Car(String model) {
       this.model = model;
   }

   public static void main(String[] args) {
       Car lamborghini  = new Car("Lamborghini Diablo");
       lamborghini = null;

   }

}

วัตถุรถ Lamborghini Diablo หยุดมีชีวิตในบรรทัดที่สองของmain()วิธีการ มีการอ้างอิงเพียงรายการเดียว จากนั้นการอ้างอิงนั้นถูกกำหนดnullเท่ากับ เนื่องจากไม่มีการอ้างอิงถึง Lamborghini Diablo เหลืออยู่ หน่วยความจำที่จัดสรรจึงกลายเป็น "ขยะ" การอ้างอิงไม่จำเป็นต้องตั้งค่าเป็นโมฆะเพื่อให้สิ่งนี้เกิดขึ้น:


public class Car {

   String model;

   public Car(String model) {
       this.model = model;
   }

   public static void main(String[] args) {
       Car lamborghini  = new Car("Lamborghini Diablo");

       Car lamborghiniGallardo = new Car("Lamborghini Gallardo");
       lamborghini = lamborghiniGallardo;
   }

}

ที่นี่เราสร้างวัตถุที่สองแล้วกำหนดวัตถุใหม่นี้ให้กับการlamborghiniอ้างอิง ตอนนี้Lamborghini Gallardoวัตถุมีการอ้างอิงสองรายการ แต่Lamborghini Diabloวัตถุไม่มี นั่นหมายความว่าDiabloตอนนี้วัตถุนั้นเป็นขยะ นี่คือเมื่อกลไกในตัวของ Java ที่เรียกว่าตัวรวบรวมขยะ (GC) เข้ามามีบทบาท

ตัวรวบรวมขยะเป็นกลไก Java ภายในที่รับผิดชอบในการเพิ่มหน่วยความจำ เช่น การลบวัตถุที่ไม่จำเป็นออกจากหน่วยความจำ มีเหตุผลที่ดีว่าทำไมเราเลือกรูปภาพหุ่นยนต์ดูดฝุ่นที่นี่ ท้ายที่สุดแล้วตัวเก็บขยะก็ทำงานในลักษณะเดียวกัน: ในพื้นหลัง มันจะ "เดินทาง" ไปตามโปรแกรมของคุณ รวบรวมขยะโดยที่คุณแทบไม่ต้องใช้ความพยายามใดๆ เลย หน้าที่ของมันคือลบวัตถุที่ไม่ได้ใช้ในโปรแกรมแล้ว

การทำเช่นนี้จะทำให้หน่วยความจำในคอมพิวเตอร์ว่างสำหรับวัตถุอื่นๆ คุณจำได้ไหมว่าในตอนต้นของบทเรียนเรากล่าวว่าในชีวิตปกติคุณต้องตรวจสอบสถานะของคอมพิวเตอร์และลบไฟล์ที่ไม่จำเป็น? ในกรณีของวัตถุ Java ตัวรวบรวมขยะจะทำสิ่งนี้ให้คุณ ตัวรวบรวมขยะจะทำงานซ้ำๆ ขณะที่โปรแกรมของคุณทำงาน: คุณไม่จำเป็นต้องเรียกหรือออกคำสั่งอย่างชัดเจน แม้ว่าจะเป็นไปได้ทางเทคนิคก็ตาม ในภายหลังเราจะพูดถึงเรื่องนี้มากขึ้นและวิเคราะห์งานโดยละเอียดยิ่งขึ้น

เมื่อตัวรวบรวมขยะไปถึงวัตถุ ก่อนที่จะทำลายวัตถุนั้น จะเรียกวิธีการพิเศษ — finalize()— กับวัตถุนั้น วิธีนี้สามารถปล่อยทรัพยากรอื่นที่ใช้โดยวัตถุ วิธีfinalize()การเป็นส่วนหนึ่งของObjectชั้นเรียน ซึ่งหมายความว่านอกเหนือจากequals(), hashCode()และtoString()เมธอดที่คุณพบก่อนหน้านี้แล้ว ทุกออบเจกต์มีเมธอดนี้ มันแตกต่างจากวิธีการอื่นตรงที่ว่า - ฉันจะใส่สิ่งนี้ได้อย่างไร - ตามอำเภอใจมาก

โดยเฉพาะอย่างยิ่งมันไม่ได้ถูกเรียกก่อนที่จะทำลายวัตถุเสมอไป การเขียนโปรแกรมเป็นความพยายามที่แม่นยำ โปรแกรมเมอร์บอกให้คอมพิวเตอร์ทำบางอย่าง และคอมพิวเตอร์ก็ทำตามนั้น ฉันคิดว่าคุณคุ้นเคยกับพฤติกรรมนี้แล้ว ดังนั้นในตอนแรกอาจเป็นเรื่องยากสำหรับคุณที่จะยอมรับแนวคิดต่อไปนี้: "ก่อนการทำลายวัตถุ วิธีการของคลาสจะถูกเรียก หรืออาจจะไม่ถูกเรียก ทั้งหมดขึ้นfinalize()อยู่Objectกับ โชคของคุณ!"

แต่มันเป็นความจริง เครื่อง Java เองจะกำหนดว่าจะเรียกfinalize()เมธอดเป็นกรณีไป หรือไม่ ตัวอย่างเช่น ลองรันโค้ดต่อไปนี้เป็นการทดสอบ:


public class Cat {

   private String name;

   public Cat(String name) {
       this.name = name;
   }

   public Cat() {
   }

   public static void main(String[] args) throws Throwable {
       for (int i = 0 ; i < 1000000; i++) {
           Cat cat = new Cat();
           cat = null; // This is when the first object becomes available to the garbage collector
       }
   }

   @Override
   protected void finalize() throws Throwable {
       System.out.println("Cat object destroyed!");
   }
}

เราสร้างCatวัตถุ และในโค้ดบรรทัดถัดไป เราตั้งค่าการอ้างอิงเดียวของวัตถุนั้นให้เป็นค่าว่าง และเราทำอย่างนั้นเป็นล้านครั้ง เราลบล้างfinalize()เมธอดอย่างชัดเจนเพื่อให้พิมพ์สตริงไปยังคอนโซลเป็นล้านครั้ง (หนึ่งครั้งต่อการทำลายอ็Catอบเจกต์แต่ละครั้ง) แต่ไม่มี! พูดให้ชัดคือมันรันบนคอมพิวเตอร์ของฉันเพียง 37,346 ครั้งเท่านั้น! นั่นคือเพียงครั้งเดียวใน 27 ครั้งที่เครื่อง Java ที่ติดตั้งในเครื่องของฉันตัดสินใจเรียกfinalize()เมธอด

ในกรณีอื่น ๆ การรวบรวมขยะเกิดขึ้นโดยไม่มีมัน ลองรันโค้ดนี้ด้วยตัวคุณเอง ส่วนใหญ่แล้วคุณจะได้ผลลัพธ์ที่แตกต่างออกไป อย่างที่คุณเห็นfinalize()แทบจะไม่สามารถเรียกได้ว่าเป็นพันธมิตรที่เชื่อถือได้ :) ดังนั้น คำแนะนำเล็กน้อยสำหรับอนาคต: อย่าพึ่งพาวิธีfinalize()การในการทำให้ทรัพยากรสำคัญฟรี บางที JVM จะเรียกมันหรืออาจจะไม่ ใครจะรู้?

หากในขณะที่อ็อบเจ็กต์ของคุณยังมีชีวิตอยู่ ทรัพยากรบางอย่างที่มีความสำคัญอย่างยิ่งต่อประสิทธิภาพการทำงาน เช่น การเชื่อมต่อฐานข้อมูลแบบเปิด จะเป็นการดีกว่าหากสร้างเมธอดพิเศษในคลาสของคุณเพื่อรีลีสแล้วเรียกใช้อย่างชัดเจนเมื่ออ็อบเจ็กต์ไม่อยู่แล้ว จำเป็น ด้วยวิธีนี้คุณจะรู้ได้อย่างแน่นอนว่าประสิทธิภาพของโปรแกรมของคุณจะไม่ได้รับผลกระทบ ตั้งแต่เริ่มแรก เรากล่าวว่าการทำงานกับหน่วยความจำและการกำจัดขยะนั้นสำคัญมาก และนี่คือความจริง การจัดการทรัพยากรอย่างไม่เหมาะสมและความเข้าใจผิดว่าออบเจกต์ที่ไม่จำเป็นได้รับการล้างข้อมูลสามารถนำไปสู่การรั่วไหลของหน่วยความจำได้ นี่เป็นหนึ่งในข้อผิดพลาดในการเขียนโปรแกรมที่เป็นที่รู้จักมากที่สุด

หากโปรแกรมเมอร์เขียนโค้ดไม่ถูกต้อง หน่วยความจำใหม่อาจถูกจัดสรรให้กับอ็อบเจกต์ที่สร้างขึ้นใหม่ในแต่ละครั้ง ในขณะที่อ็อบเจกต์เก่าที่ไม่จำเป็นอาจไม่สามารถลบออกได้โดยตัวเก็บขยะ เนื่องจากเราได้เปรียบเทียบหุ่นยนต์ดูดฝุ่น ลองจินตนาการว่าจะเกิดอะไรขึ้นถ้าคุณทำถุงเท้ากระจายก่อนเริ่มหุ่นยนต์ ทำแจกันแก้วแตก และทิ้งตัวต่อเลโก้ไว้เต็มพื้น แน่นอนว่าหุ่นยนต์จะพยายามทำงานของมัน แต่เมื่อถึงจุดหนึ่งมันก็จะติดขัด

เพื่อให้หุ่นยนต์ดูดฝุ่นทำงานได้อย่างถูกต้อง คุณต้องรักษาพื้นให้อยู่ในสภาพดีและนำสิ่งที่หุ่นยนต์ไม่สามารถจัดการออกได้ หลักการเดียวกันนี้ใช้กับตัวรวบรวมขยะของ Java หากมีวัตถุจำนวนมากเหลืออยู่ในโปรแกรมที่ไม่สามารถทำความสะอาดได้ (เช่น ถุงเท้าหรือตัวต่อเลโก้สำหรับหุ่นยนต์ดูดฝุ่นของเรา) เมื่อถึงจุดหนึ่งหน่วยความจำของคุณจะหมด และอาจไม่ใช่แค่โปรแกรมของคุณเท่านั้นที่จะค้าง — ทุกโปรแกรมที่ทำงานบนคอมพิวเตอร์อาจได้รับผลกระทบ พวกเขาอาจมีหน่วยความจำไม่เพียงพอ

คุณไม่จำเป็นต้องท่องจำสิ่งนี้ คุณเพียงแค่ต้องเข้าใจหลักการเบื้องหลังวิธีการทำงาน