java的内存模型 - stack和heap

好文直译,来源这篇文章: Stack Memory and Heap Space in Java (https://www.baeldung.com/java-stack-heap)

1. 指南

为了以最佳方式运行应用程序,JVM将内存分为堆栈和堆内存。 每当我们声明新的变量和对象,调用新的方法,声明String或执行类似的操作时,JVM就会从堆栈内存或堆空间中为这些操作指定内存。
在本教程中,我们将讨论这些内存模型。 我们将列举它们之间的一些关键区别,它们在RAM中的存储方式,它们提供的功能以及在何处使用它们。

2. java中的栈内存 - Stack

Java中的堆栈内存用于静态内存分配和线程执行。 它包括给某个方法操作的原始值(byte、short、int、long、boolean、char、float、double)以及对从该方法引用的堆中对象的引用

对该存储器的访问按后进先出(LIFO)顺序进行。 每当调用新方法时,都会在堆栈顶部创建一个新块(压栈),其中包含特定于该方法的值,例如原始变量对对象的引用

方法执行完后,将释放其对应的堆栈贞(出栈),流程返回到调用方法,并且下一个方法可以使用空间。

2.1. 栈内存几个特性

除了我们到目前为止讨论的内容以外,以下是堆栈存储器的其他一些特性:

  • 随着新的方法有序的压栈和返回,它会增长和收缩
  • 堆栈中的变量仅在创建它们的方法运行结束之前(返回)才存在 (此处原文表达不准确,已修改)
  • 栈内存被自动分配和释放 (原文表达不准确,已修改,“自动释放”应该是相对堆内存的垃圾回收来说说的吧)
  • 如果此内存已满,则Java抛出java.lang.StackOverFlowError
  • 与堆内存相比,对该内存的访问速度很快
  • 该内存是线程安全的,因为每个线程都在自己的堆栈中运行

 

3. java的堆空间 - Heap

Java中的堆空间用于在运行时为Java对象和JRE类动态分配内存。 新对象总是在堆空间中创建,并且对该对象的引用存储在栈内存中。
这些对象具有全局访问权限,可以从应用程序中的任何位置进行访问。

 

此内存模型进一步细分为称为Generation 的较小部分,它们是:

  1. 年轻代 在这里存放新创建的对象并给这些对象计时(逐渐增加对象的age,到了某个阈值(一般是15)就会被放到老年代中)填满后会发生minor垃圾回收
  2. 老年代 这是保存长期存在的对象的位置。 当对象存储在“年轻代”中时,将设置该对象的年龄阈值,当达到该阈值时,该对象将移至老一代
  3. 永生代 包含用于运行时类和应用程序方法的JVM元数据

 

3.1. java的堆的特性

除了到目前为止我们讨论的内容以外,还有堆空间的其他一些特性:

  • 可通过复杂的内存管理技术来访问它,包括年轻代,老年代及永久一代。 如果堆空间已满,Java会抛出java.lang.OutOfMemoryError
  • 对该内存的访问比堆栈内存要慢
  • 与堆栈相比,该内存不会自动释放。它需要垃圾收集器释放未使用的对象,以保持内存使用效率。
  • 与堆栈不同,堆不是线程安全的,需要通过适当的使用同步代码来加以保护

 

4. 举例说明

根据到目前为止所学的知识,我们来分析一个简单的Java代码,并评估此处的内存管理方式:

    class Person {
        int id;
        String name;
     
        public Person(int id, String name) {
            this.id = id;
            this.name = name;
        }
    }
     
    public class PersonBuilder {
        private static Person buildPerson(int id, String name) {
            return new Person(id, name);
        }
     
        public static void main(String[] args) {
            int id = 23;
            String name = "John";
            Person person = null;
            person = buildPerson(id, name);
        }
    }

 

让我们逐步分析一下:

  1. 进入main()方法后,将在堆栈存储器中创建一个空间来存储该方法的原始值和对象引用。
    • 整数id的原始值将直接存储在堆栈存储器中
    • 类型为Person的引用变量person也将在堆栈内存中创建,该内存将指向堆中的实际对象
  2. 从main()对参数化构造函数Person(int,String)的调用将在上一个堆栈的顶部分配内存。它将存储:
    • 堆栈内存中调用对象的this对象引用
    • 堆栈存储器中的原始值id
    • String参数名称的引用变量,它将指向堆内存中字符串池中的实际字符串
  3. main方法进一步调用buildPerson()静态方法,对此方法的进一步分配将在上一个方法的顶部在堆栈内存中进行。这将再次以上述方式存储变量。(我感觉Person()是后压栈的,原文应该不对)
  4. 但是,对于新创建的Person类型的对象person,所有实例变量都将存储在堆内存中。

 

下图说明了这种分配:

    

  

5. 总结

在总结本文之前,让我们快速总结一下栈内存和堆空间之间的区别:

 

比较维度
应用 栈被分块访问,在一个线程一次执行期间只访问特定的栈贞 应用运行时可以访问到整个堆空间
大小 栈空间有限制,由操作系统决定,一般比堆小 堆没有大小限制
存储 只用来存储原始值和堆中对象的引用 所有被创建的对象
顺序 先进先出(LIFO) 可通过复杂的内存管理机制访问该内存,包括年轻代,老年代以及永久一代。
生命周期 分配的栈内存的只在方法被执行时存在 对空间在程序运行是一直存在
效率 与堆相比,分配速度要快得多 与堆栈相比分配速度较慢
分配和释放 分别调用和返回方法时,将自动分配和释放此内存

当新对象被创建是分配堆空间,当栈中没有对象引用是垃圾回收器对其内存回收

 

 

当不再引用新对象时,由垃圾收集器创建并释放新对象时分配堆空间

posted @ 2020-11-11 23:07  TougherThanEvil  阅读(181)  评论(0)    收藏  举报