您的位置:首页 > 编程语言 > Java开发

java内存分配 常量池详解

2015-06-03 11:41 417 查看
在class文件中,“常量池”是最复杂也最值得关注的内容。

  Java是一种动态连接的语言,常量池的作用非常重要,常量池中除了包含代码中所定义的各种基本类型(如int、long等等)和对象型(如String及数组)的常量值还,还包含一些以文本形式出现的符号引用,比如:

  类和接口的全限定名;

  字段的名称和描述符;

  方法和名称和描述符。

  在C语言中,如果一个程序要调用其它库中的函数,在连接时,该函数在库中的位置(即相对于库文件开头的偏移量)会被写在程序中,在运行时,直接去这个地址调用函数;

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

  所以,与Java语言中的所谓“常量”不同,class文件中的“常量”内容很非富,这些常量集中在class中的一个区域存放,一个紧接着一个,这里就称为“常量池”。

转: http://hi.baidu.com/rickmeteor/blog/item/f0be11dff578ba1662279848.html
java中的常量池技术,是为了方便快捷地创建某些对象而出现的,当需要一个对象时,就可以从池中取一个出来(如果池中没有则创建一个),则在需要重复重复创建相等变量时节省了很多时间。常量池其实也就是一个内存空间,不同于使用new关键字创建的对象所在的堆空间。本文只从java使用者的角度来探讨java常量池技术,并不涉及常量池的原理及实现方法。个人认为,如果是真的专注java,就必须对这些细节方面有一定的了解。但知道它的原理和具体的实现方法则不是必须的。

1,常量池中对象和堆中的对象

public class Test{

Integer i1=new Integer(1);

Integer i2=new Integer(1);

//i1,i2分别位于堆中不同的内存空间

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

Integer i3=1;

Integer i4=1;

//i3,i4指向常量池中同一个内存空间

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

//很显然,i1,i3位于不同的内存空间

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

}

2,8种基本类型的包装类和对象池

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

public class Test{

public static void main(String[] args){

//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类也实现了常量池技术

Boolean bool1=true;

Boolean bool2=true;

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

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

Double d1=1.0;

Double d2=1.0;

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

}

}

3,String也实现了常量池技术

String类也是java中用得多的类,同样为了创建String对象的方便,也实现了常量池的技术,测试代码如下:

public class Test{

public static void main(String[] args){

//s1,s2分别位于堆中不同空间

String s1=new String("hello");

String s2=new String("hello");

System.out.println(s1==s2)//输出false

//s3,s4位于池中同一空间

String s3="hello";

String s4="hello";

System.out.println(s3==s4);//输出true

}

}

最后:

细节决定成败,写代码更是如此。

对Integer对象的补充:http://hi.baidu.com/fandywang_jlu/blog/item/c5590b4eae053cc3d1c86a13.html

Integer的封装吧:

public static Integer valueOf(int i) {

final int offset = 128;

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

return IntegerCache.cache[i + offset];

}

return new Integer(i);

}

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

Integer a = new Integer(128);

Integer b = new Integer(128);

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

Integer a = 127;

Integer b = 127;

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

结果就是:true

由上可知,我们进行对象比较时最好还是使用equals,便于按照自己的目的进行控制。

--------------------------------------------------补充-----------------------------------------------------------------------

我们看一下IntegerCache这个类里面的内容:

private static class IntegerCache {

private IntegerCache() {

}

static final Integer 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中,这样可以在一定程度上提高效率。

//////////////////////////////////////////////////////////对String方面的补充///////////////////////////////////////////////////////////

来自SUN官方文档:

3.10.5 String Literals

A string literal consists of zero or more characters enclosed in double quotes. Each character may be represented by an escape sequence.

A string literal is always of type String (§4.3.3. A string literal always refers to the same
instance (§4.3.1) of class String.

StringLiteral:

" StringCharactersopt "

StringCharacters:

StringCharacter

StringCharacters StringCharacter

StringCharacter:

InputCharacter but not " or \

EscapeSequence

The escape sequences are described in §3.10.6.

As specified in §3.4, neither of the characters CR and LF is ever considered to be an InputCharacter;
each is recognized as constituting a LineTerminator.

It is a compile-time error for a line terminator to appear after the opening " and before the closing matching ". A long string literal can always be broken up into shorter pieces and written as a (possibly parenthesized) expression using the string concatenation
operator + (§15.18.1).

The following are examples of string literals:

"" // the empty string

"\"" // a string containing " alone

"This is a string" // a string containing 16 characters

"This is a " + // actually a string-valued constant expression,

"two-line string" // formed from two string literals

Because Unicode escapes are processed very early, it is not correct to write "\u000a" for a string literal containing a single linefeed (LF); the Unicode escape \u000a is transformed into an actual linefeed in translation step 1 (§3.3) and
the linefeed becomes a LineTerminator in step 2 (§3.4), and so the string literal is not valid
in step 3. Instead, one should write "\n" (§3.10.6). Similarly, it is not correct to write "\u000d"
for a string literal containing a single carriage return (CR). Instead use "\r".

Each string literal is a reference (§4.3) to an instance (§4.3.1, §12.5)
of class String (§4.3.3). String objects have a constant value. String literals-or, more generally,
strings that are the values of constant expressions (§15.28)-are "interned" so as to share unique
instances, using the method String.intern.

Thus, the test program consisting of the compilation unit (§7.3):

package testPackage;

class Test {

public static void main(String[] args) {

String hello = "Hello", lo = "lo";

System.out.print((hello == "Hello") + " ");

System.out.print((Other.hello == hello) + " ");

System.out.print((other.Other.hello == hello) + " ");

System.out.print((hello == ("Hel"+"lo")) + " ");

System.out.print((hello == ("Hel"+lo)) + " ");

System.out.println(hello == ("Hel"+lo).intern());

}

}

class Other { static String hello = "Hello"; }

and the compilation unit:

package other;

public class Other { static String hello = "Hello"; }

produces the output:

true true true true false true

This example illustrates six points:

Literal strings within the same class (§8) in the same package (§7) represent
references to the same String object (§4.3.1).

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

Literal strings within different classes in the same package represent references to the same String object.

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

Literal strings within different classes in different packages likewise represent references to the same String object.

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

Strings computed by constant expressions (§15.28) are
computed at compile time and then treated as if they were literals.

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

Strings computed at run time are newly created and therefore distinct.

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

The result of explicitly interning a computed string is the same string as any pre-existing literal string with the same contents.

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

如果有则返回一个引用,没有则添加自己的字符串进进入常量池,

注意,只是字符串部分,

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

API解释:

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

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

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

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

所有字面值字符串和字符串赋值常量表达式都是内部的。字符串字面值在《Java Language Specification》的 §3.10.5 中已定义。

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

String s0= "java";

String s1=new String("java");

String s2=new String("java");

s1.intern();

s2=s2.intern(); //把常量池中“kvill”的引用赋给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=new String("java");

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

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

System.out.println(s2 == s1);//false

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

System.out.println( s1==s1.intern());// false

最后引出equals()和==的故事

对于字符串比较,一定要切记使用equals,勿用==
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: