Hiểu bộ nhớ trong JVM

Như bạn đã biết, JVM tự chạy các chương trình Java. Giống như bất kỳ máy ảo nào, nó có hệ thống tổ chức bộ nhớ riêng.

Bố cục bộ nhớ trong cho biết ứng dụng Java của bạn hoạt động như thế nào. Bằng cách này, các nút cổ chai trong hoạt động của các ứng dụng và thuật toán có thể được xác định. Hãy xem nó hoạt động như thế nào.

Hiểu bộ nhớ trong JVM

Quan trọng! Mô hình Java ban đầu không đủ tốt, vì vậy nó đã được sửa đổi trong Java 1.5. Phiên bản này được sử dụng cho đến ngày nay (Java 14+).

ngăn xếp chủ đề

Mô hình bộ nhớ Java được JVM sử dụng nội bộ chia bộ nhớ thành ngăn xếp luồng và đống. Hãy xem mô hình bộ nhớ Java, được chia thành các khối một cách hợp lý:

ngăn xếp chủ đề

Tất cả các luồng đang chạy trong JVM đều có ngăn xếp riêng . Ngược lại, ngăn xếp chứa thông tin về các phương thức mà luồng đã gọi. Tôi sẽ gọi đây là “ngăn xếp cuộc gọi”. Ngăn xếp cuộc gọi tiếp tục ngay sau khi luồng thực thi mã của nó.

Ngăn xếp của luồng chứa tất cả các biến cục bộ cần thiết để thực thi các phương thức trên ngăn xếp của luồng. Một luồng chỉ có thể truy cập ngăn xếp của chính nó. Các biến cục bộ không hiển thị với các luồng khác, chỉ hiển thị với luồng đã tạo ra chúng. Trong trường hợp hai luồng đang thực thi cùng một mã, cả hai đều tạo các biến cục bộ của riêng chúng. Do đó, mỗi luồng có phiên bản riêng của từng biến cục bộ.

Tất cả các biến cục bộ của các kiểu nguyên thủy ( boolean , byte , short , char , int , long , float , double ) được lưu trữ hoàn toàn trên ngăn xếp luồng và không hiển thị với các luồng khác. Một luồng có thể chuyển một bản sao của một biến nguyên thủy sang một luồng khác, nhưng không thể chia sẻ một biến cục bộ nguyên thủy.

đống

Heap chứa tất cả các đối tượng được tạo trong ứng dụng của bạn, bất kể luồng nào đã tạo đối tượng. Điều này bao gồm các hàm bao của các kiểu nguyên thủy (ví dụ: Byte , Integer , Long , v.v.). Không quan trọng đối tượng được tạo và gán cho biến cục bộ hay được tạo dưới dạng biến thành viên của đối tượng khác, nó được lưu trữ trên heap.

Dưới đây là sơ đồ minh họa ngăn xếp cuộc gọi và các biến cục bộ (chúng được lưu trữ trên ngăn xếp) cũng như các đối tượng (chúng được lưu trữ trên heap):

đống

Trong trường hợp biến cục bộ thuộc kiểu nguyên thủy, nó được lưu trữ trên ngăn xếp của luồng.

Một biến cục bộ cũng có thể là một tham chiếu đến một đối tượng. Trong trường hợp này, tham chiếu (biến cục bộ) được lưu trữ trên ngăn xếp luồng, nhưng bản thân đối tượng được lưu trữ trên heap.

Một đối tượng chứa các phương thức, các phương thức này chứa các biến cục bộ. Các biến cục bộ này cũng được lưu trữ trên ngăn xếp luồng, ngay cả khi đối tượng sở hữu phương thức được lưu trữ trên heap.

Các biến thành viên của một đối tượng được lưu trữ trên heap cùng với chính đối tượng đó. Điều này đúng cả khi biến thành viên thuộc kiểu nguyên thủy và khi nó là tham chiếu đối tượng.

Các biến lớp tĩnh cũng được lưu trữ trên heap cùng với định nghĩa lớp.

Tương tác với các đối tượng

Các đối tượng trên heap có thể được truy cập bởi tất cả các luồng có tham chiếu đến đối tượng. Nếu một luồng có quyền truy cập vào một đối tượng, thì nó có thể truy cập các biến của đối tượng. Nếu hai luồng gọi một phương thức trên cùng một đối tượng cùng một lúc, cả hai sẽ có quyền truy cập vào các biến thành viên của đối tượng, nhưng mỗi luồng sẽ có bản sao riêng của các biến cục bộ.

Tương tác với các đối tượng (heap)

Hai luồng có một tập hợp các biến cục bộ.Biến cục bộ 2trỏ đến một đối tượng được chia sẻ trên heap (đối tượng 3). Mỗi luồng có bản sao riêng của biến cục bộ với tham chiếu riêng. Các tham chiếu của chúng là các biến cục bộ và do đó được lưu trữ trên ngăn xếp luồng. Tuy nhiên, hai tham chiếu khác nhau trỏ đến cùng một đối tượng trên heap.

Xin lưu ý rằng chungđối tượng 3có liên kết đếnđối tượng 2đối tượng 4dưới dạng các biến thành viên (được hiển thị bằng mũi tên). Thông qua các liên kết này, hai luồng có thể truy cậpđối tượng 2Sự vật4.

Sơ đồ cũng cho thấy một biến cục bộ (biến cục bộ 1từ methodTwo ). Mỗi bản sao của nó chứa các tham chiếu khác nhau trỏ đến hai đối tượng khác nhau (đối tượng 1đối tượng 5) và không giống nhau. Về mặt lý thuyết, cả hai luồng có thể truy cập cả haiđối tượng 1, vậy đểđối tượng 5nếu chúng có tham chiếu đến cả hai đối tượng này. Nhưng trong sơ đồ trên, mỗi luồng chỉ có một tham chiếu đến một trong hai đối tượng.

Một ví dụ về tương tác với các đối tượng

Hãy xem cách chúng ta có thể chứng minh công việc bằng mã:

public class MySomeRunnable implements Runnable() {

    public void run() {
        one();
    }

    public void one() {
        int localOne = 1;

        Shared localTwo = Shared.instance;

        //... do something with local variables

        two();
    }

    public void two() {
        Integer localOne = 2;

        //... do something with local variables
    }
}
public class Shared {

    // store an instance of our object in a variable

    public static final Shared instance = new Shared();

    // member variables pointing to two objects on the heap

    public Integer object2 = new Integer(22);
    public Integer object4 = new Integer(44);
}

Phương thức run() gọi phương thức one()one() lần lượt gọi two() .

Phương thức one() khai báo một biến cục bộ nguyên thủy (địa phươngMột) kiểu int và một biến cục bộ (địa phươngHai), là một tham chiếu đến một đối tượng.

Mỗi luồng thực thi phương thức one() sẽ tạo bản sao của chính nóđịa phươngMộtđịa phươngHaitrong ngăn xếp của bạn. Biếnđịa phươngMộtsẽ hoàn toàn tách biệt với nhau, nằm trên ngăn xếp của mỗi luồng. Một luồng không thể thấy những thay đổi mà luồng khác tạo ra đối với bản sao của nóđịa phươngMột.

Mỗi luồng thực thi phương thức one() cũng tạo bản sao của chính nóđịa phươngHai. Tuy nhiên, hai bản khác nhauđịa phươngHaicuối cùng trỏ đến cùng một đối tượng trên heap. Sự thật làđịa phươngHaitrỏ đến đối tượng được tham chiếu bởi biến tĩnhví dụ. Chỉ có một bản sao của một biến tĩnh và bản sao đó được lưu trữ trên heap.

Vì vậy cả hai bảnđịa phươngHaicuối cùng trỏ đến cùng một phiên bản được chia sẻ . Phiên bản được chia sẻ cũng được lưu trữ trên heap. Nó phù hợpđối tượng 3trong sơ đồ trên.

Lưu ý rằng lớp Chia sẻ cũng chứa hai biến thành viên. Bản thân các biến thành viên được lưu trữ trên heap cùng với đối tượng. Hai biến thành viên trỏ đến hai đối tượng khácsố nguyên. Các đối tượng số nguyên này tương ứng vớiđối tượng 2đối tượng 4trên sơ đồ.

Cũng lưu ý rằng phương thức two() tạo một biến cục bộ có tênđịa phươngMột. Biến cục bộ này là một tham chiếu đến một đối tượng kiểu Integer . Phương thức đặt liên kếtđịa phươngMộtđể trỏ đến một thể hiện Integer mới . Liên kết sẽ được lưu trữ trong bản sao của nóđịa phươngMộtcho mỗi chủ đề. Hai phiên bản Số nguyên sẽ được lưu trữ trên heap và vì phương thức tạo một đối tượng Số nguyên mới mỗi khi nó được thực thi, hai luồng thực thi phương thức này sẽ tạo các phiên bản Số nguyên riêng biệt . Họ phù hợpđối tượng 1đối tượng 5trong sơ đồ trên.

Cũng lưu ý hai biến thành viên trong lớp Chia sẻ của loại Số nguyên , là loại nguyên thủy. Vì các biến này là biến thành viên nên chúng vẫn được lưu trữ trên heap cùng với đối tượng. Chỉ các biến cục bộ được lưu trữ trên ngăn xếp luồng.