商城首页欢迎来到中国正版软件门户

您的位置: 首页 > 文章列表 > 编程开发 > Java堆、java栈

Java堆、java栈

  发布于2026-05-21 阅读(0)

扫一扫,手机访问

Ja va堆:

一启动Ja va虚拟机,那块最大的内存区域——Ja va堆,就被创建出来了。它可以说是垃圾收集器工作的“主战场”,被所有线程共享,其核心使命就是存放各种对象实例。

为了更高效地管理内存和进行垃圾回收,堆空间被划分成了老年代和新生代。老年代,顾名思义,存放的是那些“长寿”对象,要么是经过多次垃圾回收(GC)依然坚挺的“老兵”,要么是那些一出生就过于庞大、新生代根本放不下的“巨婴”。而新生代,则是新创建对象的“摇篮”,它内部又细分为三个区域:Eden区、From Survivor区(简称From区)和To Survivor区(简称To区)。具体结构可以参考下图:

一个新对象诞生后,首先会被安置在新生代的Eden区,这里也是新生代最主要的内存区域。当Eden区快满时,会触发一次针对新生代的垃圾回收(Minor GC)。在这次回收中存活下来的对象,会被移动到From区。

接下来的过程就有点“二人转”的味道了。From区里的对象,经过下一次GC后,存活下来的会被转移到To区。而To区里的对象再经历GC时,存活者又会被挪回From区。这里的关键在于,From区和To区的角色并不是固定的,它们会随着GC算法的执行而相互转换。严格来说,每次都是“当前存活的From区”经过GC后,将幸存者送入“当前空闲的To区”。

那么,对象什么时候才能“晋升”到老年代呢?通常,在From区和To区之间来回“折腾”了足够多次(默认是15次)后,如果依然存活,就证明它是一个值得长期持有的对象,于是便会被转移到老年代中。

堆内存的优势非常明显:可以动态地按需分配内存大小,对象的生命周期也无需事先声明,不再使用的数据会有垃圾收集器自动清理。但凡事都有两面性,这种动态分配的灵活性,带来的代价就是存取速度相对较慢。

Ja va栈:

与共享的堆不同,Ja va栈是线程私有的,它的生命周期与所属线程完全绑定。栈的核心作用,是为Ja va方法的执行提供内存模型。每个方法被执行时,都会在栈中创建一个对应的“栈帧”,用来存储这个方法的局部变量表、操作数栈、动态链接、方法出口等信息。可以说,一个方法从被调用到执行完毕的过程,就对应着它的栈帧在虚拟机栈中从入栈到出栈的完整旅程。

栈的优势在于速度极快,其存取效率仅次于CPU内部的寄存器。但它的缺点也同样突出:存放在栈中的数据,其大小和生存期都必须在编译期就确定下来,缺乏堆那种运行时动态调整的灵活性。

从代码中分析堆、栈空间:

理论说了这么多,不如看一段具体的代码来得直观。我们来看下面这个例子:

public class Test1 {
    public void m1(){
        int a = 3;
        Person person = new Person();
        person.setName("name");
        m2();
    }
    public void m2(){
        int a = 3;
        Person person = new Person();
        person.setAge(20);
        Person person2 = person;
        person2.setName("name2");
    }
}

这段代码里,Test1类定义了m1()m2()两个方法。我们来一步步拆解,看看其中的变量在JVM的堆和栈中是如何分布的。其内存模型可以参考下图:

  1. 执行m1()方法:JVM会为m1()在栈中开辟一个栈帧。首先,存放基本类型变量int a = 3;接着,定义引用类型变量Person person,并执行new Person()——这一步是关键,它会在中开辟一块新空间,用于存储这个新创建的Person对象实例,栈中的person变量则保存着指向堆中该对象的引用地址。
  2. 调用person.setName("name"):这行代码通过栈中person变量持有的引用,找到堆中对应的Person对象,并修改其name属性。
  3. 调用m2()方法:此时,JVM会为m2()在栈顶开辟一个新的栈帧(位于m1栈帧之上)。同样,先存放int a = 3;然后定义Person person并执行new Person(),这会在中再次开辟一块新的空间,创建另一个Person对象。
  4. 执行Person person2 = person:这是理解引用类型的关键。这行代码并没有在堆中创建新的对象,它仅仅是将栈中person变量保存的引用地址,复制了一份给person2变量。结果是,personperson2这两个栈中的变量,指向了堆中同一个Person对象。
  5. 通过person2修改对象:当执行person2.setName("name2")时,通过person2的引用修改了堆中那个唯一对象的数据。此时,通过person变量去访问这个对象,其name属性也同步变成了“name2”。

这里引出了一个至关重要的概念:基本数据类型与引用数据类型的根本区别

基本数据类型(如int, short, long, byte, float, double, boolean, char,共8种,注意String不属于基本类型)的变量,存储的是实际的“字面值”。比如int a = 3;,这个“3”就直接存放在栈帧的局部变量表中。因为它们大小固定、生命周期明确(通常随方法结束而消失),出于追求极致速度的考虑,被安排在栈中。栈还有一个优化:对于字面值相同的变量,可能会共享同一份数据。例如同时定义int a = 3;int b = 3;,编译器可能会让ab指向栈中同一个字面值“3”。

引用数据类型(即对象类型)则完全不同。当我们写Person person = new Person();时,new Person()这个动作在中创建了对象的实际数据,而栈中的变量person仅仅保存了一个指向堆中那个对象的“地址引用”。因此,当多个引用变量(如personperson2)被赋值为指向同一个堆对象时,无论通过其中哪一个变量去修改对象状态,所有指向该对象的变量所看到的内容都会同步改变。这正是上例中第4、5步所演示的情况。

本文转载于:https://blog.csdn.net/zhoushimiao1990/article/details/90599276 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注