Java中常量池、堆和栈的区别对比与联系

 更新时间:2025年09月02日 15:38:08   作者:Wokoo7  
Java中堆存储对象实例和字符串(JDK7+),栈保存方法调用信息与局部变量,常量池缓存编译期常量,三者通过引用关联,堆与栈区分对象创建与存储,常量池优化内存复用,本文给大家介绍Java中常量池、堆和栈的区别对比与联系,感兴趣的朋友一起看看吧

在 Java 中,常量池、堆、栈是 JVM 内存模型中三个核心的内存区域,各自承担不同的职责,其位置、存储内容和特性有显著区别,但又相互关联。下面详细解析:

一、基本概念与存储内容

1. 堆(Heap)

  • 位置:JVM 中最大的内存区域,属于线程共享区域。
  • 存储内容:
    所有对象实例(包括通过new创建的对象、数组)和字符串常量池(JDK7 及之后) 都存储在这里。
    例如:new Object()new int[10]new String("abc")创建的对象,以及 JDK7 + 的字符串常量池。
  • 特点
    • 内存动态分配,大小不固定,可随程序运行扩展。
    • 由垃圾回收器(GC)管理内存回收,当对象无引用时会被回收。
    • 访问速度较慢(相比栈)。

2. 栈(虚拟机栈,VM Stack)

  • 位置:线程私有,每个线程创建时会分配一个独立的栈,与线程生命周期一致。
  • 存储内容
    栈帧(Stack Frame) 为单位存储,每个方法调用时会创建一个栈帧,包含:
    • 局部变量表(方法内的局部变量,如int a = 1; String s;);
    • 操作数栈(方法执行时的临时数据操作);
    • 方法返回地址(方法执行完毕后回到调用处的地址)等。
      例如:main方法调用时,会生成一个栈帧,其中的String s(局部变量)就存储在局部变量表中。
  • 特点
    • 内存大小固定(可通过 JVM 参数配置),遵循 “先进后出”(FILO)原则。
    • 方法执行结束后,栈帧自动销毁,内存无需 GC 干预,效率极高。
    • 访问速度快(相比堆),因为栈是连续的内存空间。

3. 常量池(Constant Pool)

常量池并非单一区域,而是一个 “存储常量的集合”,细分为Class 常量池、运行时常量池、字符串常量池,其位置和作用不同:

类型位置(JDK8+)存储内容
Class 常量池.class 文件中(加载后进入元空间)类编译时生成的常量,如字面量(字符串、数字)、符号引用(类名、方法名)等。
运行时常量池元空间(本地内存,方法区实现)Class 常量池加载到内存后的表现形式,常量在此处被解析为直接引用(如对象地址)。
字符串常量池堆内存存储字符串字面量(如"abc"),用于复用相同内容的字符串,减少内存消耗。
  • 核心特点
    • 存储的都是 “常量”(编译期或运行期确定的不变值),如字符串字面量、基本类型常量final int a = 10等。
    • 字符串常量池是最常被讨论的,例如String s = "abc"中,"abc"会被放入字符串常量池,后续相同字面量会直接复用。

二、三者的区别

维度堆(Heap)栈(VM Stack)常量池(以字符串常量池为例)
存储内容对象实例、数组、字符串常量池(JDK7+)局部变量、栈帧(方法调用信息)字符串字面量、基本类型常量等
线程共享性线程共享(所有线程可访问同一对象)线程私有(每个线程有独立的栈)字符串常量池线程共享;Class 常量池随类加载,线程共享
内存管理由垃圾回收器(GC)回收随线程 / 方法结束自动释放(栈帧弹出)字符串常量池中的常量在无引用时被 GC 回收
内存大小大(可动态扩展)小(固定,易栈溢出)中等(依赖常量数量)
访问速度慢(内存不连续,需 GC 管理)快(内存连续,无 GC 干预)较快(复用机制减少创建开销)
生命周期随对象引用存在而存在随线程或方法调用周期存在随类加载 / 常量创建而存在,无引用时销毁

三、三者的联系

三个区域并非孤立,而是通过 “引用” 相互关联,共同支撑 Java 程序的运行:

  1. 栈 → 堆:栈中的局部变量(引用类型)指向堆中的对象。
    例如:String s = new String("abc")中,s是栈中的局部变量,指向堆中new String("abc")创建的对象。

堆 → 字符串常量池:堆中的字符串对象可能引用字符串常量池中的字面量。

  • 例如:String s = "abc"中,堆中可能创建一个 String 对象(若常量池无"abc"),该对象引用字符串常量池中的"abc";后续 String s2 = "abc"会直接复用常量池中的"abc",堆中无需重复创建。
  • 栈 → 常量池:栈中的局部变量可直接引用常量池中的常量。
  • 例如:final String s = "abc"中,s(栈中)直接引用字符串常量池中的"abc"
  • 方法调用时的协作
  • 调用方法时,栈中创建栈帧(存储局部变量),局部变量若为引用类型,则指向堆中的对象或常量池中的常量;方法执行中需要的常量(如字符串)从运行时常量池获取。

四、举例说明三者关系

public class MemoryDemo {
    public static void main(String[] args) {
        // 1. "hello"放入字符串常量池(堆中);
        //    s1(栈中)引用常量池中的"hello"
        String s1 = "hello"; 
        // 2. 堆中创建新对象(内容为"hello"),该对象引用常量池中的"hello";
        //    s2(栈中)指向堆中的新对象
        String s2 = new String("hello"); 
        int a = 10; // 3. a是局部变量,直接存储在栈的局部变量表中
    }
}
  • s1(栈)→ 字符串常量池(堆)中的"hello"
  • s2(栈)→ 堆中的new String对象 → 引用字符串常量池中的"hello"
  • a(栈)直接存储值10(基本类型,无需堆或常量池)。

五、字符串常量池与堆内存

重要判断技巧: 

  • 双引号 ("")里的内容, 都会存放在常量池中
  • new 出来的对象都在堆内存 ;此时,堆中的对象与字符串常量池无关
  • 只要是new的对象,都是唯一的。
public static void main(String[] args) {
    String s1 = "hello";
    String s2 = "hello";
    String s3 = new String("hello");
    String s4 = new String("hello");
    System.out.println(s1 == s2);    // true
    System.out.println(s1 == s3);    // false
    System.out.println(s3 == s4);    // false
}

总结

  • 是对象的 “仓库”,存储所有动态创建的实例;
  • 是方法执行的 “工作台”,存储局部变量和调用信息,速度快但空间有限;
  • 常量池是 “常量缓存区”,存储不变值以复用,减少内存浪费。

到此这篇关于Java中常量池、堆和栈的区别与联系的文章就介绍到这了,更多相关java常量池堆和栈内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • java 数据结构基本算法希尔排序

    java 数据结构基本算法希尔排序

    这篇文章主要介绍了数据结构基本算法希尔排序的相关资料,需要的朋友可以参考下
    2017-08-08
  • 8个简单部分开启Java语言学习之路 附java学习书单

    8个简单部分开启Java语言学习之路 附java学习书单

    8个简单部分开启Java语言学习之路,附java学习书单,这篇文章主要向大家介绍了学习java语言的方向,感兴趣的小伙伴们可以参考一下
    2016-09-09
  • java实现构造无限层级树形菜单

    java实现构造无限层级树形菜单

    这篇文章主要介绍了java实现构造无限层级树形菜单,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-09-09
  • spring mvc实现登录账号单浏览器登录

    spring mvc实现登录账号单浏览器登录

    这篇文章主要为大家详细介绍了spring mvc实现登录账号单浏览器登录,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-04-04
  • SpringBoot server.port配置原理详解

    SpringBoot server.port配置原理详解

    这篇文章主要介绍了Spring Boot server.port配置原理详解,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-12-12
  • Java stringBuilder的使用方法及实例解析

    Java stringBuilder的使用方法及实例解析

    这篇文章主要介绍了Java stringBuilder的使用方法及实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-09-09
  • Mybatis学习总结之mybatis使用建议

    Mybatis学习总结之mybatis使用建议

    这篇文章主要介绍了Mybatis学习总结之mybatis使用建议的相关资料,非常具有参考借鉴价值,需要的朋友可以参考下
    2016-05-05
  • Java调用参数类型是application/x-www-form-urlencoded的API问题

    Java调用参数类型是application/x-www-form-urlencoded的API问题

    在使用Postman进行接口测试时,对于POST请求,需将请求头设置为application/x-www-form-urlencoded,并将参数转为String类型,通常在GET请求中,参数直接拼接在URL后,本文通过具体实例,详细讲解了参数处理的方法,适合API开发者参考
    2024-09-09
  • Springboot登录验证的统一拦截处理的实现

    Springboot登录验证的统一拦截处理的实现

    如果不进行统一的拦截处理,每次用户请求你都要去进行用户的信息验证,所以本文主要介绍了Springboot登录验证的统一拦截处理的实现,感兴趣的可以了解一下,感兴趣的可以了解一下
    2023-09-09
  • 深入理解Java抽象类

    深入理解Java抽象类

    这篇文章主要介绍了Java抽象类的相关资料,帮助大家更好的理解和学习Java,感兴趣的朋友可以了解下
    2020-08-08

最新评论