JAVA常量池

本文详细解释了Java中的常量池概念,如何在编译期和运行期处理字符串常量,以及String对象的内存管理机制。通过实例演示了常量池如何优化内存使用,以及String对象在不同情况下如何共享或独占内存资源。

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

---不知道哪里转来的.忘了

Java程序运行在JVM上,JVM实现了java程序的平台无关性。Java的内存分配都在JVM中进行。

JVM为每个已加载的类维护一个常量池。用来保存 基本数据类型,String,对其他类,方法,字段的符号引用。

Java语言中不是这样,一切都是动态的。编译时,如果发现对其它类方法的调用或者对其它类字段的引用的语句,记录进class文件中的只能是一个文本形式的符号引用,在连接过程中,虚拟机根据这个文本信息去查找对应的方法或字段。

 

什么是常量池

它是在栈中的一块内存区,保存了基本数据类型,String,包含一些以文本形式出现的符号引用,比如:

类和接口的全限定名;

字段的名称和描述符;

方法的名称和描述符;

 

在常量池中保存的内容有一个特性:他们都是contanst的,在运行中不会改变!

比如下面小段源码中粗体代码显示的部分:

publicclass ClassTest {

private String itemS ="我们 ";

private final int itemI =100 ;

public void setItemS (String para ){...}

}

 

具体的话,常量池中的常量类型如下

常量表类型

标志值(1 byte)

描述

CONSTANT_Utf8

1

UTF-8编码的Unicode字符串

CONSTANT_Integer

3

int类型的字面值

CONSTANT_Float

4

float类型的字面值

CONSTANT_Long

5

long类型的字面值

CONSTANT_Double

6

double类型的字面值

CONSTANT_Class

7

对一个类或接口的符号引用

CONSTANT_String

8

String类型字面值的引用

CONSTANT_Fieldref

9

对一个字段的符号引用

CONSTANT_Methodref

10

对一个类中方法的符号引用

CONSTANT_InterfaceMethodref

11

对一个接口中方法的符号引用

CONSTANT_NameAndType

12

对一个字段或方法的部分符号引用

 

String为何也可以支持常量池技术
Java源代码中的每一个字面值字符串/String,都会在编译成class文件阶段,形成标志号为8(CONSTANT_String_info)的常量表JVM加载 class文件的时候,会为对应的常量池建立一个内存数据结构,并存放在方法区中。同时JVM会自动为CONSTANT_String_info常量表中的字符串常量的字面值在堆中创建新的String对象(intern字符串对象,又叫拘留字符串对象)。然后把CONSTANT_String_info常量表的入口地址转变成这个堆中String对象的直接地址(常量池解析)


  
为什么String支持常量池?直接证据在哪里?

我们看一下String.javaintern是如何定义的呢。

/**

     * Returns an interned string equal to thisstring. The VM maintains an internal set of

     * unique strings. All string literalsfound in loaded classes'

     * constant pools are automaticallyinterned. Manually-interned strings are only weakly

     * referenced, so calling {@code intern}won't lead to unwanted retention.

     *

     * <p>Interning is typically usedbecause it guarantees that for interned strings

     * {@code a} and {@code b}, {@codea.equals(b)} can be simplified to

     * {@code a == b}. (This is not true ofnon-interned strings.)

     *

     * <p>Many applications find itsimpler and more convenient to use an explicit

     * {@link java.util.HashMap} to implementtheir own pools.

     */

public native String intern();

也就是说,intern会最终链接到JVM。返回的值都在VM控制的常量池中。

 

举例说明:

(1)String s=newString("Hello world");

在运行这段指令之前,JVM就已经为"Helloworld"在堆中创建了一个拘留字符串( 值得注意的是:如果源程序中还有一个"Hello world"字符串常量,那么他们都对应了同一个堆中的拘留字符串)。然后,用这个拘留字符串的值来初始化堆中用new出来的String对象,局部变量s存储的new出来的堆对象地址拘留字符串在堆中

(2)Strings="Hello world";

这跟(1)中创建指令有很大的不同,此时局部变量s存储的是早已创建好的拘留字符串的堆地址

 

 

常量池技术如何确认存在?

举例来说明。以String为例。

这里首先提一下:equals:比较的是内容。==:比较的是对象

1.   String s1=new String("hello");
String s2=new String("hello");
System.out.println(s1==s2);  // 输出false
s1, s2
产生的是对象,对象的内存保存在堆中。在class load过程中,是动态产生的,所以不一样。
String 对象(内存)的不变性机制会使修改String字符串时,产生大量的对象,因为每次改变字符串,都会生成一个新的String java 为了更有效的使用内存,常量池在编译期遇见String 字符串时,它会检查该池内是否已经存在相同的String 字符串,如果找到,就把新变量的引用指向现有的字符串对象,不创建任何新的String 常量对象,没找到再创建新的。所以对一个字符串对象的任何修改,都会产生一个新的字符串对象,原来的依然存在,等待垃圾回收。

2.   String s3="hello";
String s4="hello";
System.out.println(s3==s4);
// 输出true
s3, s4
赋以相同的值,在栈中指向的是相同的常量对象

3.   String a = “test”;
String b = “test”;
b = b+"java";
a
b同时指向常量池中的常量值"test"b=b+"java"之后,b原先指向一个常量,内容为"test”,通过对b进行+"java"操作后,b之前所指向的那个值没有改变,但此时b不指向原来那个变量值了,而指向了另一个String变量,内容为”test java“。原来那个变量还存在于内存之中,只是b这个变量不再指向它了。

 

八种基本类型的包装类和对象池

网上说,java中基本类型的包装类的大部分都实现了常量池技术,这些类是Byte,Short,Integer,Long,Character,Boolean,另外两种浮点数类型的包装类则没有实现。另外Byte,Short,Integer,Long,Character5种整型的包装类也只是在对应值小于等于127时才可使用常量池,也即对象不负责创建和管理大于127的这些类的对象.

继续举例说明:

//5种整形的包装类Byte,Short,Integer,Long,Character的对象,

//在值小于127时可以使用常量池

Integer i1=127;

Integer i2=127;

System.out.println(i1==i2);//输出true

 

//值大于127时,不会从常量池中取对象

Integer i3=128;

Integer i4=128;

System.out.println(i3==i4);//输出false

 

//Boolean类也实现了常量池技术

Booleanbool1=true;

Booleanbool2=true;

System.out.println(bool1==bool2);//输出true

 

//浮点类型的包装类没有实现常量池技术

Double d1=1.0;

Double d2=1.0;

System.out.println(d1==d2);//输出false

 

对Integer对象的代码补充

public staticInteger valueOf(int i) {

final int offset = 128;

if (i >= -128 && i <= 127) {

return IntegerCache.cache[i + offset];

}

return new Integer(i);

}

当你直接给一个Integer对象一个int值的时候,其实它调用了valueOf方法,然后你赋的这个值很特别,是128,那么没有进行cache方法,相当于new了两个新对象。所以问题中定义ab的两句代码就类似于:

Integer a = newInteger(128);

Integer b = newInteger(128);

这个时候再问你,输出结果是什么?你就知道是false了。如果把这个数换成127,再执行:

Integer a = 127;

Integer b = 127;

System.out.println(a== b);

结果就是:true

 

补充:看一下IntegerCache这个类里面的内容:

private staticclass IntegerCache {

private IntegerCache() {

}

static finalInteger cache[] = new Integer[-(-128) + 127 + 1];

static {

for (int i = 0; i < cache.length; i++)

cache[i] = new Integer(i - 128);

}

}

由于cache[]IntegerCache类中是静态数组,也就是只需要初始化一次,即static{......}部分,所以,如果Integer对象初始化时是-128~127的范围,就不需要再重新定义申请空间,都是同一个对象---IntegerCache.cache中,这样可以在一定程度上提高效率。

 

 

---------------------------------------------------------------------------------------------------

实际情况是我用Eclipse直接编译出错。Jdk版本是1.8.xxx

jdk5.0之后就应该好了才是。在JDK5.0之后,Integer i3 = 127是允许的。

针对百度百科:Integer常量池中的说明。

尼玛,为何一个针对integer的缓存就成了常量池技术呢。我竟无言以对。。。

 

 

针对String方面的补充

在同包同类下,引用自同一String对象.

在同包不同类下,引用自同一String对象.

在不同包不同类下,依然引用自同一String对象.

在编译成.class时能够识别为同一字符串的,自动优化成常量,所以也引用自同一String对象.

在运行时创建的字符串具有独立的内存地址,所以不引用自同一String对象.

Stringintern()方法会查找在常量池中是否存在一份equal相等的字符串,

如果有则返回一个引用,没有则添加自己的字符串进入常量池,注意:只是字符串部分。

所以这时会存在2份拷贝,常量池的部分被String类私有并管理,自己的那份按对象生命周期继续使用。

返回字符串对象的规范化表示形式

一个初始值为空的字符串池,它由类 String 私有地维护。

当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(该对象由 equals(Object) 方法确定),则返回池中的字符串引用。否则,将此 String 对象添加到池中,并且返回此 String 对象的引用。

它遵循对于任何两个字符串 s t,当且仅当 s.equals(t) true 时,s.intern() == t.intern() 才为 true

所有字面值字符串和字符串赋值常量表达式都是内部的。

------------------------------------代码演示补充-------------------------------------

String s0="java";

String s1=newString("java");

String s2=newString("java");

s1.intern();

s2=s2.intern();//把常量池中"java"的引用赋给s2

System.out.println(s0==s1);//false “ intern返回的引用没有引用变量接收~ s1.intern();等于废代码.”

System.out.println(s0==s1.intern() );//true

System.out.println(s0==s2 );//true

------------------------------------代码演示补充-------------------------------------

String s1=newString("java");

Strings2=s1.intern();//s1 检查常量池,发现没有就拷贝自己的字符串进去

//s2 引用该字符串常量池的地址

System.out.println(s2== s1);//false----对比常量池跟对象引用

System.out.println(s2==s1.intern());//true---对比两个常量池的

System.out.println(s1==s1.intern());// false---s1是对象的地址,s1.intern是保存的值

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值