JVM 中的內存

開放

了解 JVM 中的內存

如您所知,JVM 在其內部運行 Java 程序。像任何虛擬機一樣,它有自己的內存組織系統。

內部存儲器佈局指示 Java 應用程序的工作方式。通過這種方式,可以識別應用程序和算法運行中的瓶頸。讓我們看看它是如何工作的。

了解 JVM 中的內存

重要的!原來的Java模型不夠好,所以在Java 1.5中進行了修改。這個版本一直沿用至今(Java 14+)。

線程棧

JVM內部使用的Java內存模型將內存分為線程棧和堆。我們來看一下Java內存模型,邏輯上分為塊:

線程棧

JVM 中運行的所有線程都有自己的堆棧。反過來,堆棧保存有關線程調用了哪些方法的信息。我將其稱為“調用堆棧”。一旦線程執行其代碼,調用堆棧就會恢復。

線程的堆棧包含在線程堆棧上執行方法所需的所有局部變量。一個線程只能訪問它自己的堆棧。局部變量對其他線程不可見,僅對創建它們的線程可見。在兩個線程執行相同代碼的情況下,它們都創建自己的局部變量。因此,每個線程都有自己的每個局部變量版本。

所有基本類型的局部變量(booleanbyteshortcharintlongfloatdouble)都完全存儲在線程堆棧中,對其他線程不可見。一個線程可以將原始變量的副本傳遞給另一個線程,但不能共享原始局部變量。

堆包含在您的應用程序中創建的所有對象,無論哪個線程創建了該對象。這包括基本類型的包裝器(例如,ByteIntegerLong等)。不管對像是創建並分配給局部變量還是作為另一個對象的成員變量創建的,它都存儲在堆上。

下圖說明了調用堆棧和局部變量(它們存儲在堆棧中)以及對象(它們存儲在堆中):

堆

在局部變量是原始類型的情況下,它存儲在線程的堆棧中。

局部變量也可以是對對象的引用。在這種情況下,引用(局部變量)存儲在線程堆棧中,但對象本身存儲在堆中。

一個對象包含方法,這些方法包含局部變量。這些局部變量也存儲在線程堆棧中,即使擁有該方法的對象存儲在堆中。

對象的成員變量與對象本身一起存儲在堆中。當成員變量是原始類型和對象引用時都是如此。

靜態類變量也與類定義一起存儲在堆中。

與對象的交互

所有引用該對象的線程都可以訪問堆上的對象。如果一個線程可以訪問一個對象,那麼它就可以訪問該對象的變量。如果兩個線程同時調用同一個對象的方法,它們都可以訪問該對象的成員變量,但每個線程都有自己的局部變量副本。

與對象的交互(堆)

兩個線程有一組局部變量。局部變量 2指向堆上的共享對象(對象 3). 每個線程都有自己的局部變量副本和自己的引用。它們的引用是局部變量,因此存儲在線程堆棧中。但是,兩個不同的引用指向堆上的同一個對象。

請注意,一般對象 3有鏈接到對象 2對象 4作為成員變量(如箭頭所示)。通過這些鏈接,兩個線程可以訪問對象 2目的4.

該圖還顯示了一個局部變量(局部變量 1來自methodTwo)。它的每個副本都包含指向兩個不同對象的不同引用(對象 1對象 5) 而不是同一個。理論上,兩個線程都可以訪問對象 1, 所以對象 5如果他們引用了這兩個對象。但是在上圖中,每個線程只有對兩個對象之一的引用。

與對象交互的示例

讓我們看看如何在代碼中演示這項工作:

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

run()方法調用one()方法,然後one()又調用two()

one()方法聲明了一個原始局部變量 (本地一個) 類型的int和一個局部變量 (本地二),這是對對象的引用。

每個執行one()方法的線程都會創建自己的副本本地一個本地二在你的堆棧中。變量本地一個將彼此完全分開,在每個線程的堆棧上。一個線程看不到另一個線程對其副本所做的更改本地一個.

每個執行one()方法的線程也會創建自己的副本本地二. 但是,兩個不同的副本本地二最終指向堆上的同一個對象。事實是本地二指向靜態變量引用的對象實例. 靜態變量只有一個副本,並且該副本存儲在堆中。

所以兩個副本本地二最終指向同一個共享實例。Shared實例也存儲在堆上。它匹配對象 3在上圖中。

請注意,Shared類還包含兩個成員變量。成員變量本身與對像一起存儲在堆上。兩個成員變量指向另外兩個對象整數. 這些整數對像對應於對象 2對象 4在圖上。

另請注意,two()方法創建了一個名為本地一個. 此局部變量是對Integer類型對象的引用。該方法設置鏈接本地一個指向一個新的Integer實例。該鏈接將存儲在其副本中本地一個對於每個線程。堆上會存儲兩個Integer實例,由於該方法每次執行都會創建一個新的Integer對象,所以執行該方法的兩個線程會創建單獨的Integer實例。他們匹配對象 1對象 5在上圖中。

還要注意Shared類中類型為Integer的兩個成員變量,這是一種原始類型。因為這些變量是成員變量,所以還是和對像一起存放在堆上。只有局部變量存儲在線程堆棧中。

留言
  • 受歡迎
你必須登入才能留言
此頁面尚無留言