CHÀO!

Tôi nghĩ bạn sẽ không quá ngạc nhiên nếu tôi nói với bạn rằng máy tính của bạn có dung lượng bộ nhớ hạn chế :) Ngay cả ổ cứng — thường lớn hơn bộ nhớ RAM nhiều lần — cũng có thể chứa đầy dung lượng với các trò chơi, chương trình TV yêu thích của bạn, và hơn thế nữa. Để ngăn điều này xảy ra, bạn cần theo dõi trạng thái hiện tại của bộ nhớ và xóa các tệp không cần thiết khỏi máy tính của mình. Lập trình Java có liên quan gì đến tất cả những điều này? Mọi thứ! Xét cho cùng, khi máy Java tạo bất kỳ đối tượng nào, nó sẽ cấp phát bộ nhớ cho đối tượng đó.

Trong một chương trình lớn thực sự, hàng chục, hàng trăm nghìn đối tượng được tạo và mỗi đối tượng được cấp phát một phần bộ nhớ riêng cho nó. Nhưng bạn nghĩ tất cả những đồ vật này tồn tại trong bao lâu? Họ có "sống" trong toàn bộ thời gian chương trình của chúng tôi đang chạy không? Dĩ nhiên là không. Ngay cả với tất cả các ưu điểm của các đối tượng Java, chúng không phải là bất tử :) Các đối tượng có vòng đời riêng của chúng. Hôm nay chúng ta sẽ tạm nghỉ viết mã một chút và xem xét quy trình này :) Hơn nữa, điều rất quan trọng là bạn phải hiểu cách thức hoạt động của một chương trình và cách quản lý tài nguyên. Vì vậy, khi nào cuộc sống của một đối tượng bắt đầu? Giống như một người - từ khi sinh ra, tức là sáng tạo.


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

Đầu tiên, Máy ảo Java phân bổ lượng bộ nhớ cần thiết để tạo đối tượng. Sau đó, nó tạo một tham chiếu đến bộ nhớ đó. Trong trường hợp của chúng tôi, tham chiếu đó được gọi là cat, vì vậy chúng tôi có thể theo dõi nó. Sau đó, tất cả các biến của nó được khởi tạo, hàm tạo được gọi và — ta-da! - đối tượng mới đúc của chúng ta đang sống cuộc sống của chính nó :)

Tuổi thọ của các đối tượng khác nhau, vì vậy chúng tôi không thể cung cấp con số chính xác ở đây. Trong mọi trường hợp, nó tồn tại một thời gian bên trong chương trình và thực hiện các chức năng của nó. Nói chính xác, một đối tượng là "sống" miễn là có các tham chiếu đến nó. Ngay khi không còn tham chiếu nào, đối tượng sẽ "chết". Ví dụ:


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;

   }

}

Đối tượng xe hơi Lamborghini Diablo ngừng hoạt động trên dòng thứ hai của phương main()thức. Chỉ có một tham chiếu đến nó và sau đó tham chiếu đó được đặt bằng null. Vì không còn tham chiếu nào đến Lamborghini Diablo, bộ nhớ được cấp phát sẽ trở thành "rác". Một tham chiếu không cần phải được đặt thành null để điều này xảy ra:


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;
   }

}

Ở đây chúng tôi đã tạo một đối tượng thứ hai và sau đó gán đối tượng mới này cho tham lamborghinichiếu. Bây giờ Lamborghini Gallardođối tượng có hai tham chiếu, nhưng Lamborghini Diablođối tượng không có. Điều đó có nghĩa là Diablođối tượng bây giờ là rác. Đây là lúc cơ chế tích hợp sẵn của Java được gọi là bộ thu gom rác (GC) phát huy tác dụng.

Trình thu gom rác là một cơ chế nội bộ của Java chịu trách nhiệm giải phóng bộ nhớ, tức là loại bỏ các đối tượng không cần thiết khỏi bộ nhớ. Có lý do chính đáng khiến chúng tôi chọn hình ảnh robot hút bụi ở đây. Xét cho cùng, trình thu gom rác hoạt động theo cùng một cách: ở chế độ nền, nó "di chuyển" về chương trình của bạn, thu gom rác mà bạn thực tế không cần nỗ lực gì. Công việc của nó là loại bỏ các đối tượng không còn được sử dụng trong chương trình.

Làm điều này giải phóng bộ nhớ trong máy tính cho các đối tượng khác. Bạn có nhớ rằng ở phần đầu của bài học, chúng tôi đã nói rằng trong cuộc sống bình thường, bạn phải theo dõi trạng thái của máy tính và xóa các tệp không cần thiết không? Chà, trong trường hợp các đối tượng Java, trình thu gom rác sẽ làm việc này cho bạn. Bộ thu gom rác chạy lặp đi lặp lại khi chương trình của bạn chạy: bạn không cần phải gọi nó hoặc ra lệnh cho nó một cách rõ ràng, mặc dù điều này có thể thực hiện được về mặt kỹ thuật. Sau này chúng ta sẽ nói về nó nhiều hơn và phân tích công việc của nó chi tiết hơn.

Khi trình thu gom rác tiếp cận một đối tượng, ngay trước khi hủy đối tượng, nó sẽ gọi một phương thức đặc biệt — finalize()— trên đối tượng. Phương pháp này có thể giải phóng các tài nguyên khác được sử dụng bởi đối tượng. Phương thức này finalize()là một phần của Objectlớp. Điều đó có nghĩa là ngoài các phương thức equals(), hashCode()toString()mà bạn đã gặp trước đây, mọi đối tượng đều có phương thức này. Nó khác với các phương pháp khác ở chỗ - tôi nên nói thế nào đây - rất thất thường.

Đặc biệt, không phải lúc nào nó cũng được gọi trước khi một đối tượng bị hủy. Lập trình là một nỗ lực chính xác. Lập trình viên yêu cầu máy tính làm một việc gì đó và máy tính sẽ làm việc đó. Tôi cho rằng bạn đã quen với hành vi này nên ban đầu có thể bạn sẽ khó chấp nhận ý kiến ​​sau: "Trước khi hủy đối tượng, phương thức finalize()của Objectlớp được gọi. Hoặc có thể không được gọi. Tất cả phụ thuộc vào May mắn của bạn!"

Tuy nhiên, đó là sự thật. Máy Java tự quyết định có gọi finalize()phương thức đó hay không tùy theo từng trường hợp. Ví dụ: hãy thử chạy đoạn mã sau dưới dạng thử nghiệm:


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!");
   }
}

Chúng tôi tạo một Catđối tượng và sau đó trong dòng mã tiếp theo, chúng tôi đặt tham chiếu duy nhất của nó bằng null. Và chúng tôi làm điều đó một triệu lần. Chúng tôi đã ghi đè finalize()phương thức một cách rõ ràng để nó in một chuỗi ra bàn điều khiển một triệu lần (một lần cho mỗi lần nó phá hủy một Catđối tượng). Nhưng không! Nói chính xác, nó chỉ chạy 37.346 lần trên máy tính của tôi! Tức là, chỉ một lần trong 27 lần máy Java được cài đặt trên máy của tôi quyết định gọi finalize()phương thức này.

Trong các trường hợp khác, việc thu gom rác xảy ra mà không có nó. Hãy thử tự chạy mã này: rất có thể bạn sẽ nhận được một kết quả khác. Như bạn có thể thấy, finalize()khó có thể được gọi là một đối tác đáng tin cậy :) Vì vậy, một lời khuyên nhỏ cho tương lai: đừng dựa vào phương finalize()pháp giải phóng các tài nguyên quan trọng. Có thể JVM sẽ gọi nó, hoặc có thể không. Ai biết?

Nếu trong khi đối tượng của bạn còn sống, nó chứa một số tài nguyên cực kỳ quan trọng đối với hiệu suất, chẳng hạn như kết nối cơ sở dữ liệu mở, thì tốt hơn là tạo một phương thức đặc biệt trong lớp của bạn để giải phóng chúng và sau đó gọi nó một cách rõ ràng khi đối tượng không còn nữa cần thiết. Bằng cách đó, bạn sẽ biết chắc chắn rằng hiệu suất chương trình của bạn sẽ không bị ảnh hưởng. Ngay từ đầu, chúng tôi đã nói rằng làm việc với bộ nhớ và loại bỏ rác là rất quan trọng, và điều này là đúng. Xử lý tài nguyên không đúng cách và hiểu sai cách dọn dẹp các đối tượng không cần thiết có thể dẫn đến rò rỉ bộ nhớ. Đây là một trong những lỗi lập trình nổi tiếng nhất.

Nếu các lập trình viên viết mã của họ không chính xác, bộ nhớ mới có thể được phân bổ cho các đối tượng mới được tạo mỗi lần, trong khi các đối tượng cũ, không cần thiết có thể không có sẵn để loại bỏ bởi trình thu gom rác. Vì chúng ta đã làm một phép tương tự với máy hút bụi rô-bốt, hãy tưởng tượng điều gì sẽ xảy ra nếu trước khi khởi động rô-bốt, bạn rải tất khắp nhà, làm vỡ một chiếc bình thủy tinh và để các khối xếp hình Lego vương vãi khắp sàn nhà. Tất nhiên, robot sẽ cố gắng thực hiện công việc của mình, nhưng đến một lúc nào đó, nó sẽ gặp khó khăn.

Để robot hút bụi hoạt động bình thường, bạn cần giữ sàn ở tình trạng tốt và loại bỏ mọi thứ mà robot không thể xử lý. Nguyên tắc tương tự áp dụng cho bộ thu gom rác của Java. Nếu còn nhiều đối tượng trong chương trình không thể dọn sạch (chẳng hạn như một chiếc tất hoặc khối xây dựng Lego cho máy hút bụi rô-bốt của chúng tôi), đến một lúc nào đó, bạn sẽ hết bộ nhớ. Và có thể không chỉ chương trình của bạn bị đóng băng — mọi chương trình khác đang chạy trên máy tính đều có thể bị ảnh hưởng. Họ cũng có thể không có đủ bộ nhớ.

Bạn không cần phải ghi nhớ điều này. Bạn chỉ cần hiểu nguyên tắc đằng sau cách thức hoạt động của nó.