了解 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类型的两个成员变量,这是一种基本类型。因为这些变量是成员变量,所以还是和对象一起存放在堆上。只有局部变量存储在线程堆栈中。