JVM-常量池

本文深入探讨了JVM中的常量池概念及其工作原理,特别是针对String和Integer类型的动态常量池机制进行了详细的解释,并提供了具体的代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

学习JVM之后,发现对常量池的理解又深入了一些。下面就从JVM的层面去理解JAVA程序中那些常见的语句。
读过笔者博客“JVM-类的生命周期“[url]http://yizhenn.iteye.com/blog/2290619[/url]的读者都应该已经知道,JVM在加载class文件时经历了装载,连接,初始化的过程,其中连接又包括验证,准备和解析。我们就来说说这个解析。

[b]所谓解析,就是将class文件中的静态常量池中的符号引用解析为直接引用,说白了,就是为class常量池中的一些常量创建对象。[/b]比如String str="abc";理论上来讲直接在class静态常量池中存放"abc"即可。但是我们知道,JAVA中除了8种基本类型之外,其他的都是引用。因此,对于静态常量池中的"abc",JVM会在堆中创建一个对象,静态常量池中的str就指向这个刚刚创建的对象。像str这样的常量对象所在的空间,叫做动态常量池。

在JAVA中,String和Integer,Short,Long,Character,Boolean,Byte这六种基本类型的封装类都实现了动态常量池机制。对于六种基本类型的封装类,我们以Integer为代表进行说明。

如下代码:

String a="123";
String b="123";

在解析的时候,发现静态常量池中有a="123",就会在动态常量池中寻找是否有值为"123"的对象,结果没有找到,就会执行new String("123"),然后使静态常量池中的a指向该对象,当发现静态常量池中有b="123",就会在动态常量池中寻找是否有值为"123"的对象,结果找到了之前创建的那个对象,就会把那个对象的地址返回给静态常量池中的b。

上面的过程是在解析的时候做的,当然你也可以认为在执行的时候做的。但不管怎样,一定存在一个常量池,[color=red][b]当执行String str="xx";的时候,先到常量池中去寻找值为xx的对象,如果存在,就直接返回该对象地址,否则在动态常量池中创建一个新对象并返回地址。[/b][/color]

如下代码:

String a=new String("123");
String b=new String("123");

第一行代码在执行的时候,[color=red][b]JVM见到"123",会先到动态常量池中看是否有值为"123"的对象,如果没有,在常量池中创建一个对象String("123").这个过程和a没有半毛钱关系,你可以认为这是JVM常量池自学习的过程。接着,在堆区创建一个对象String("123");并将该对象的引用返回给a;[/b][/color]
第二行代码也是这样的过程,[b]所以上面的两行代码可能产生2或3个对象。[/b]

对于String类,还存在一个[color=red][b]str.intern()方法,他的作用是检查动态常量池中是否存在值与str对象相同的对象,如果存在,直接返回该对象的引用。如果不存在,就在常量池中创建一个对象,然后返回他的引用。[/b][/color]
如下代码:
String a="123";
String b="123";
String c=new String("123");
String d=c.intern();

对于上面的代码,你应该能理解,a和b和d都指向的是动态常量池中的那个对象String("123"),而c指向的是堆中的对象String("123");

介绍了String类的常量池,我们来说Integer类的常量池。Integer类型的取值不像String类型那么广泛,[color=red][b]String常量池中的对象收集自程序。Integer常量池中的对象是固定的,只有取值为-128~127的这256个对象。当Integer.valueOf(i)中i的值是-128~127时,直接返回常量池中的对象,否则在堆区新建一个对象并返回他的引用[/b][/color]。这可以从jdk源码中得到证明,在jdk源码中,Integer有这样的一个方法:
    public static Integer valueOf(int i) {
assert IntegerCache.high >= 127;
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}


这个方法是自动装包时候用的方法,何谓装包?把int这个基本类型转化为Integer这个引用类型就是装包。
代码如下:
Integer a=1;
Integer b=1;
Integer c=1111;
Integer d=1111;

对于上面的代码,实际上如下:
Integer a=Integer.valueOf(1);
Integer b=Integer.valueOf(1);
Integer c=Integer.valueOf(1111);
Integer d=Integer.valueOf(1111);

因此a和b指向Integer常量池中的同一个对象,他们的值相等。c和d指向堆区中的不同对象,他们的值不等。

如下代码:

Integer a=new Integer(1);
Integer b=new Integer(1);

由于使用了new,强制的在堆区创建了两个对象,没用到常量池优化,因此a和b的值不等。

[color=red][b]这里顺便提一下装包和拆包,将基本类型转化为对应的封装类的过程叫装包,反之叫拆包。当基本类型和其对应的封装类执行比较的时候,比如Integer对象和int型比较,这会使用拆包,将Integer类型拆包为int,这个过程调用Integer的intValue()方法。[/b][/color]

关于上面提到的其余5中包装类的常量池与Integer类似,在此不再赘述。
### JVM 常量池的分类 JVMJava 虚拟机)中的常量池是一个非常重要的概念,主要用于存储类和接口中的一些静态信息。根据其作用范围和生命周期,JVM 常量池可以分为以下三类: 1. **静态常量池(Class Constant Pool)** 静态常量池是在类编译成 `.class` 文件时生成的,它存在于每个类或接口的字节码文件中。静态常量池主要包含两类信息: - 字面量:例如字符串常量、基本数据类型的值等。 - 符号引用:例如类和接口的全限定名、字段的名称和描述符、方法的名称和描述符等。 静态常量池是类的元信息的一部分,属于方法区的内容,在类加载时会被读取并转换为运行时常量池。 2. **运行时常量池(Runtime Constant Pool)** 运行时常量池是静态常量池JVM 运行时的表示形式,它与静态常量池相对应,并且包含了更多动态的信息。当类被加载到 JVM 中时,静态常量池中的符号引用会被解析为实际的内存地址,这些解析后的信息会保存在运行时常量池中。运行时常量池还支持动态语言特性,比如通过 `String.intern()` 方法动态添加字符串常量[^4]。 每个类或接口在加载后都会拥有一个独立的运行时常量池,它是方法区的一部分。 3. **字符串常量池(String Constant Pool)** 字符串常量池是一种特殊的运行时常量池,专门用于存储字符串常量。它的设计目的是为了提高性能和减少内存开销,通过共享机制避免重复创建相同的字符串对象。 在 Java 7 及之前的版本中,字符串常量池位于永久代(Permanent Generation),而从 Java 8 开始,永久代被元空间(Metaspace)取代,字符串常量池也随之迁移到堆内存中[^3]。这一变化使得垃圾回收器能够更高效地管理字符串常量池的内存。 在调用 `String.intern()` 方法时,JVM 会检查字符串常量池中是否已经存在相同的字符串。如果存在,则返回池中的引用;如果不存在,则将该字符串加入池中并返回其引用。需要注意的是,在 JDK 1.6 和 JDK 1.7 的实现中,`intern()` 的行为有所不同: - 在 JDK 1.6 中,如果字符串常量池中没有对应的字符串,JVM 会复制一份字符串实例到常量池中,然后返回该副本的引用。 - 在 JDK 1.7 及以上版本中,如果字符串常量池中没有对应的字符串,JVM 直接将堆中的字符串对象引用存入常量池,无需额外复制操作[^1]。 ### 示例代码 以下代码展示了字符串常量池的基本行为: ```java public class StringPoolExample { public static void main(String[] args) { String s1 = new String("hello"); String s2 = s1.intern(); String s3 = "hello"; System.out.println(s1 == s2); // false System.out.println(s2 == s3); // true } } ``` 在这个例子中,`s1` 是通过 `new String("hello")` 创建的,它会在堆中生成一个新的字符串对象。`s1.intern()` 将 `"hello"` 添加到字符串常量池中,并返回池中的引用 `s2`。由于 `"hello"` 已经存在于常量池中,`s3` 会直接指向池中的对象,因此 `s2 == s3` 返回 `true`。 --- ### 总结 JVM 常量池可以分为三类:静态常量池、运行时常量池和字符串常量池。它们分别对应类文件中的静态信息、运行时的动态解析结果以及字符串的共享机制。理解这些常量池的作用及其在不同 JVM 版本中的变化,有助于更好地掌握 Java 的内存管理和性能优化策略。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值