---不知道哪里转来的.忘了
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.java中intern是如何定义的呢。
/**
* 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,Character这5种整型的包装类也只是在对应值小于等于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了两个新对象。所以问题中定义a、b的两句代码就类似于:
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对象.
String的intern()方法会查找在常量池中是否存在一份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是保存的值