java常量池

一 常量池示例

复制代码
    /**
     *  字符串:"奶茶" 存在堆内存的 字符串常量池。
     * 其他常量(如数字、类名):存在方法区(元空间)的 运行时常量池。
     *
     */
    public static void main(String[] args) {

        String s1 = "奶茶";  // 第一次写"奶茶",存到公共菜单(常量池)
        String s2 = "奶茶";  // 直接拿菜单上的"奶茶",不用新做一杯
        String s3 = new String("奶茶"); // 强行做一杯新的,但原料还是菜单上的"奶茶" 。重新创建了一个对象,堆中的地址不同。
        先在堆常量池创建一份(如果没有),然后再在堆中创建一个对象。

//        s1="12";
        System.out.println(s1 == s2);
        System.out.println(s1 == s3);

        System.out.println(s1 .equals(s2) );
        System.out.println(s1 .equals(s3) );

    }
}

二 说明

1

创建方式的本质区别

s1 = "奶茶"

这是通过字符串字面量(String Literal)​创建的字符串。

JVM 会先在字符串常量池​(位于堆内存中)中查找是否有 "奶茶":

如果存在,直接复用该对象 (地址相同)。

如果不存在,在常量池中新建一个对象

​特点:多个相同的字面量会指向同一个对象。

2

s3 = new String("奶茶")

这是通过 ​new 关键字在堆内存中强制创建新对象

JVM 会:

先在字符串常量池中检查 "奶茶" 是否存在(如果不存在,则先在常量池中创建 )。
然后在堆内存中创建一个全新的 String 对象 ,内容与 "奶茶" 相同,但地址不同。

​特点:每次 new 都会生成新对象,即使内容相同。

二 常量池存储位置

1

复制代码
public class Example {
    // 运行时常量池(元空间):存储符号引用和字面量 "Hello"
    private static final String MESSAGE = "Hello";

    public static void main(String[] args) {
        // 字符串常量池(堆内存):存储 "Hello"
        String s1 = "Hello";
        // 堆内存:创建新对象,但引用字符串常量池的 "Hello"
        String s2 = new String("Hello");
        // 字符串常量池(堆内存):新增 "World"
        String s3 = "World";
    }
}

二 运行时常量

1 运行时常量池 ≠ static final 变量

​1. static final 变量的存储

​基本类型(如 static final int MAX = 100)​:

值直接存储在运行时常量池中。

​引用类型(如 static final String S = "OK")​:

引用(指针)存储在运行时常量池中,实际对象(如 "OK")在堆内存的字符串常量池中。

2

运行时常量池的内容远不止 static final

除了 static final 变量,运行时常量池还包含:

​类的符号引用:如 java/lang/String。

​方法的符号引用:如 java/io/PrintStream.println。

​动态生成的常量:如通过 String.intern() 添加的字符串。

​字段的符号引用:如 MyClass.count。

​字面量:如代码中直接写的 "Hello" 或 123。

三 关键总结

1 ​Class 文件常量池:

静态数据,存储在 .class 文件中,包含符号引用和字面量。

例如:java/lang/String 的符号引用、字符串 "Hello" 的字面量。

2 ​运行时常量池:

动态数据,存储在方法区(元空间),包含解析后的直接引用和运行时生成的常量。

​不仅仅是 static final 变量:还包含类、方法、字段的元数据,以及动态内容(如 intern() 的字符串)。

3 ​static final 变量的特殊性

基本类型的 static final 变量直接存储在运行时常量池。

引用类型的 static final 变量存储引用(在运行时常量池 ),对象在堆中

4 Class 文件常量池 vs 运行时常量池

核心区别
对比项 Class 文件常量池 运行时常量池
存储位置 编译后的 .class 文件中(静态文件) JVM 内存的方法区(元空间)中(动态运行时数据)
存储内容 符号引用(类名、方法名、字段名等)、字面量(数值、字符串) Class 文件常量池的副本 + 运行期动态添加的常量(如 String.intern() 的字符串)
生命周期 永久(随 .class 文件存在) 类加载时创建,类卸载时销毁
是否可修改 不可修改(静态编译数据) 可动态扩展(例如运行时添加新的常量)
符号引用解析 未解析(如 java/lang/Object 已解析为直接引用(如内存地址)

四 常见误区

1

​误区:认为运行时常量池只存 static final 变量。

​纠正:运行时常量池的核心是类元数据,static final 变量只是其中一部分。

2

​误区:认为 static final String S = new String("OK") 的 "OK" 在运行时常量池。

​纠正:new String("OK") 的 "OK" 在堆中,但字面量 "OK" 的引用在运行时常量池。

五 String s2 = new String("Hello");为什么创建两个对象?

1 为什么会有两个对象?

当执行 String s2 = new String("Hello"); 时,JVM 会执行以下步骤:

​检查字符串常量池:

首先检查字符串常量池中是否存在字面量 "Hello"。

如果存在,直接复用该对象。

如果不存在,在字符串常量池中创建一个新的 "Hello" 对象。

​在堆中创建新对象:
无论常量池中是否存在 "Hello",new String("Hello") 都会在堆内存中创建一个全新的 String 对象,内容与常量池中的 "Hello" 相同。

结果

​常量池对象:字面量 "Hello" 的对象(共享)。

​堆对象:new 创建的全新 String 对象(独立)。

2 看似重复,但逻辑合理:

字符串常量池的设计是为了 ​共享不可变对象,减少内存占用。

new 关键字的设计是为了 ​显式创建独立对象,满足某些特殊场景需求(如需要不同实例)。

六 字符串常量池在堆内存中吗?

是的!​Java 7 及之后版本,字符串常量池的位置从永久代(PermGen)迁移到了堆内存(Heap)。

​Java 6 及之前:字符串常量池在永久代。

​Java 7+:字符串常量池在堆内存中。

迁移原因:

​永久代大小固定:容易导致 OutOfMemoryError: PermGen space。

​堆内存动态扩展:字符串常量池可以随堆内存动态调整,避免内存溢出。

​垃圾回收:堆内存中的字符串常量池对象可以被垃圾回收(无引用时)。

2 内存结构示意图

复制代码
+---------------------+      +---------------------+
|   字符串常量池(堆内存)  |      |       堆内存          |
|---------------------|      |---------------------|
|   String对象 "Hello"   | <---- s1(引用地址相同)       |
+---------------------+      |                     |
                             |   new String("Hello") | <---- s2(全新对象)
                             +---------------------+

s1 = "Hello" :直接指向字符串常量池中的对象。

s2 = new String("Hello") :指向堆中的新对象,但字面量 "Hello" 会先在常量池中创建(如果不存在)。


3 总结

操作 结果
String s1 = "Hello"; 直接使用字符串常量池中的对象(无重复创建)。
String s2 = new String("Hello"); 先在常量池中创建 "Hello"(如不存在),再在堆中创建新对象(可能重复,但逻辑合理)。

字符串常量池在堆内存中 :Java 7+ 的优化设计,避免永久代内存溢出。

重复创建的合理性new 关键字的设计目标与常量池不同,需根据场景选择。

相关推荐
敖云岚24 分钟前
【云原生技术】容器技术的发展史
开发语言·云原生·perl
云上艺旅32 分钟前
K8S学习之基础三十一:k8s中RBAC 的核心概念
java·学习·云原生·kubernetes
忧郁的蛋~40 分钟前
JavaScript性能优化的12种方式
开发语言·javascript·性能优化
人工智能研究所42 分钟前
使用OpenCV与Python编写自己的俄罗斯方块小游戏
开发语言·python·opencv
DDD小小小宇宙42 分钟前
python列表基础知识
开发语言·windows·python
海盗强44 分钟前
prototype和proto的区别
开发语言·javascript·原型模式
追寻光1 小时前
Java 绘制图形验证码
java·前端
2301_792185881 小时前
maven的安装配置
java·maven
霸王龙的小胳膊1 小时前
SpringMVC-文件上传
java·mvc
哥谭居民00011 小时前
mybatis注册一个自定义拦截器,拦截器用于自动填充字段
java·开发语言·jvm·mybatis