Java中的堆栈

时间:2019-04-29 03:17:56  来源:igfitidea点击:

为了深入了解 Java 或任何其他 OOP 语言(如 C# )中的面向对象编程。我们必须知道Java 进程和 JVM是如何在内部管理事情。当然,Java语法和Java的OOP原则实现很重要,
但如果将问题超出 我如何完成?我如何编写?,那么对于应用程序资源、内存、性能、参数传递、线程和垃圾回收,我们将有一个更清晰的印象。真正的问题应该是如何或为什么它发生这样的事情?

在这篇文章中,我们将介绍如何管理应用程序变量,关于它们存储的位置(堆栈或堆)以及存储时间。

什么是栈(stack)和堆(heap)

为了简单明了,我们说应用程序正在通过将数据存储到随机存查内存中的内存区域来处理数据。(如果我们知道一些汇编程序背景,你会看到这是一种肤浅的方法)
这些区域称为:

[栈:Stack]

  • 操作系统为进程保留的内存空间;

  • 堆栈大小是固定的,在编译器阶段根据变量声明和其他编译器选项确定;

  • 重要的是要确定堆栈是有限的,其大小是固定的(一个进程已经开始,它不能改变堆栈大小);

  • 大多数时候,它用于存储函数/方法变量(输入参数和局部变量)的堆栈。

  • 每种方法都有自己的堆栈(进程堆栈中的区域),包括 [main] ,这也是一个函数。

  • 方法堆栈仅在该方法的生存期内存在:从调用时刻到返回时刻;

[堆:Heap]

  • 由操作系统管理并由进程用于在运行时获取额外空间的内存空间;

  • 此区域是全局的,这意味着任何进程都可以使用它(当然,进程不能在另一个进程堆保留区域中读取或写入);

  • 此内存的作用是向运行时需要补充空间的进程提供额外的内存资源(例如,我们有一个简单的 Java 应用程序,该应用程序正在构造一个包含控制台值的数组);

  • 进程运行时所需的空间由new 函数(请记住,它与用于在 Java 中创建对象时相同的函数)确定,这些函数用于在 Heap 中获取额外空间。

栈和堆中分别存储什么

根据以前的规则,让我们分析一下这个简单的Java应用程序:

class Student{

  int age;               //instance variable

  String name;     //instance variable

  public Student()

  {

      this.age = 0;

      name = "匿名";

  }

  public Student(int Age, String Name)

  {

      this.age = Age;

      setName(Name);

  }

  public void setName(String Name)

  {

      this.name = Name;

  }

}

public class Main{

	public static void main(String[] args) {

          Student s;                   //local variable - reference

          s = new Student(19,"Tom");

          int noStudents = 1;          //local variable

	}

}

为了确定最小空间(因为我们只关注重要元素,并且使事情尽可能简单),让我们分析堆栈要求。

我们从 [main] 方法开始,因为对于 Java 过程,一切都以 [main] 开头和结束。

[main] 局部变量或存储在堆栈上的变量有:

  • 引用的参数(它是字符串数组);

  • 学生参考,名为s;

  • 整数值(4 字节),名为 noStudents;

默认构造函数局部变量是:

  • 当前创建的对象引用,名为 [this];

  • ([记住:] 对象及其值存储在堆中)。

第二个构造函数(具有参数的构造函数)局部变量:

  • 当前创建对象的引用,名为 [this];

  • 输入的参数,Age,整数值

  • 输入的参数,Name,字符串引用

setName方法的局部变量是:

  • 调用对象引用,名为 this;([记住:] 每个非静态类方法由对象调用,并且该对象引用将以this传递给该方法)

  • 输入参数,Name,字符串引用

正如我前面所说,所有这些方法堆栈实际上是单个应用程序堆栈的一部分。每个部件都存在于函数生存期内。如果分析这些方法调用的顺序,可以定义更大的图[进程调用堆栈]。

对于上一个示例,当执行具有参数的构造函数时,调用堆栈如下所示:

java-8-understand-stack-and-heap

可以看到,调用堆栈由所有活动方法生成。因为main方法是进程入口点,所以它是调用堆栈上的第一个方法。在此时刻之后,每次调用方法时,它都将放置在调用堆栈上。

对于上一示例,当它从 Student构造函数中调用 setName方法时。 调用堆栈此时的大小最大。

堆中的存储情况

在 堆Heap 中存储了使用 new 运算符创建的所有值,这意味着大多数对象值。因此,对于前面的示例,引用都存储在方法的栈中,对象存储在堆中:

java-8-understand-stack-and-heap

可以看到存储在其栈中的方法局部变量。此外,我们还可以看到对象值(Student值和字符串值)都存储在堆中。

堆的值由以下参数创建:

  • 堆中的学生内存区域通过调用 new 运算符和 类构造函数创建;
  • String 值是在类构造函数内的对象初始化期间创建的。

栈变量和堆变量的生存期

有关变量生存期的一般规则是,它们至少存在于我们需要的时间。

对于栈变量,因为它们位于方法堆栈上,因此只要执行该方法,它们就存在。因为 [main] 方法是一种特殊方法(进程以 main 开头和结尾),因此在整个过程执行过程中都存在其局部变量。对于其他方法,它们的堆栈仅存在于我们调用该方法直到结束的那一刻起(返回或由于异常)。

堆(对象值)中的值存在,只要我们的引用具有堆中该内存区域的地址。如果无法通过引用到达堆内存区域(该引用不存在,或者它具有另一个值/地址),则垃圾收集器将释放该空间。

其他类型的变量(如类静态属性)的管理方式相同,在将该堆栈用于方法堆栈之前,它们存储在进程堆栈中。