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

java JVM常量池和八种基本数据及字符串

2014-07-13 23:24 225 查看
    常量池(constant_pool)指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。它包括了关于类、方法、接口等中的常量,也包括字符串常量和符号引用。运行时常量池是方法区的一部分。

     在Class文件结构中,最头的4个字节用于存储魔数Magic Number,用于确定一个文件是否能被JVM接受,再接着4个字节用于存储版本号,前2个字节存储次版本号,后2个存储主版本号,再接着是用于存放常量的常量池,由于常量的数量是不固定的,所以常量池的入口放置一个U2类型的数据(constant_pool_count)存储常量池容量计数值。常量池主要用于存放两大类常量:字面量(Literal)和符号引用量(Symbolic References),字面量相当于Java语言层面常量的概念,如文本字符串,声明为final的常量值等,符号引用则属于编译原理方面的概念,包括了如下三种类型的常量:

     类和接口的全限定名

     字段名称和描述符

     方法名称和描述符

 

     Java中八种基本类型的包装类的大部分都实现了常量池技术,它们是Byte、Short、Integer、Long、Character、Boolean,另外两种浮点数类型的包装类(Float、Double)则没有实现。另外Byte,Short,Integer,Long,Character这5种整型的包装类也只是在对应值在-128到127时才可使用对象池。

 

     看下面代码:

Java代码  


/** 

 * Huisou.com Inc. 

 * Copyright (c) 2011-2012 All Rights Reserved. 

 */  

  

/** 

 * @description 

 * @package 

 * @title Test.java 

 * @author chenzehe 

 * @email 

 * @version 

 * @updateUser 

 * @create 2011-12-21 上午11:27:48 

 * @update 2011-12-21 上午11:27:48 

 */  

  

public class Test {  

    public static void main(String[] args) throws Exception {  

        Integer a = 127;  

        Integer b = 127;  

        Integer c = 128;  

        Integer d = 128;  

        System.out.println(a == b);// 输出true  

        System.out.println(c == d);// 输出false  

    }  

}  

      使用javap查看生成的字节码:

Java代码  


E:\chenzehe\workspace_b2b_3th\test\src>javap -verbose Test  

Compiled from "Test.java"  

public class Test extends java.lang.Object  

  SourceFile: "Test.java"  

  minor version: 0  

  major version: 50  

  Constant pool:  

const #1 = Method       #6.#21; //  java/lang/Object."<init>":()V  

const #2 = Method       #22.#23;        //  java/lang/Integer.valueOf:(I)Ljava/lang/Integer;  

const #3 = Field        #24.#25;        //  java/lang/System.out:Ljava/io/PrintStream;  

const #4 = Method       #26.#27;        //  java/io/PrintStream.println:(Z)V  

const #5 = class        #28;    //  Test  

const #6 = class        #29;    //  java/lang/Object  

const #7 = Asciz        <init>;  

const #8 = Asciz        ()V;  

const #9 = Asciz        Code;  

const #10 = Asciz       LineNumberTable;  

const #11 = Asciz       main;  

const #12 = Asciz       ([Ljava/lang/String;)V;  

const #13 = Asciz       StackMapTable;  

const #14 = class       #30;    //  "[Ljava/lang/String;"  

const #15 = class       #31;    //  java/lang/Integer  

const #16 = class       #32;    //  java/io/PrintStream  

const #17 = Asciz       Exceptions;  

const #18 = class       #33;    //  java/lang/Exception  

const #19 = Asciz       SourceFile;  

const #20 = Asciz       Test.java;  

const #21 = NameAndType #7:#8;//  "<init>":()V  

const #22 = class       #31;    //  java/lang/Integer  

const #23 = NameAndType #34:#35;//  valueOf:(I)Ljava/lang/Integer;  

const #24 = class       #36;    //  java/lang/System  

const #25 = NameAndType #37:#38;//  out:Ljava/io/PrintStream;  

const #26 = class       #32;    //  java/io/PrintStream  

const #27 = NameAndType #39:#40;//  println:(Z)V  

const #28 = Asciz       Test;  

const #29 = Asciz       java/lang/Object;  

const #30 = Asciz       [Ljava/lang/String;;  

const #31 = Asciz       java/lang/Integer;  

const #32 = Asciz       java/io/PrintStream;  

const #33 = Asciz       java/lang/Exception;  

const #34 = Asciz       valueOf;  

const #35 = Asciz       (I)Ljava/lang/Integer;;  

const #36 = Asciz       java/lang/System;  

const #37 = Asciz       out;  

const #38 = Asciz       Ljava/io/PrintStream;;  

const #39 = Asciz       println;  

const #40 = Asciz       (Z)V;  

  

{  

public Test();  

  Code:  

   Stack=1, Locals=1, Args_size=1  

   0:   aload_0  

   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V  

   4:   return  

  LineNumberTable:  

   line 18: 0  

  

  

public static void main(java.lang.String[])   throws java.lang.Exception;  

  Code:  

   Stack=3, Locals=5, Args_size=1  

   0:   bipush  127  

   2:   invokestatic    #2; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;  

   5:   astore_1  

   6:   bipush  127  

   8:   invokestatic    #2; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;  

   11:  astore_2  

   12:  sipush  128  

   15:  invokestatic    #2; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;  

   18:  astore_3  

   19:  sipush  128  

   22:  invokestatic    #2; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;  

   25:  astore  4  

   27:  getstatic       #3; //Field java/lang/System.out:Ljava/io/PrintStream;  

   30:  aload_1  

   31:  aload_2  

   32:  if_acmpne       39  

   35:  iconst_1  

   36:  goto    40  

   39:  iconst_0  

   40:  invokevirtual   #4; //Method java/io/PrintStream.println:(Z)V  

   43:  getstatic       #3; //Field java/lang/System.out:Ljava/io/PrintStream;  

   46:  aload_3  

   47:  aload   4  

   49:  if_acmpne       56  

   52:  iconst_1  

   53:  goto    57  

   56:  iconst_0  

   57:  invokevirtual   #4; //Method java/io/PrintStream.println:(Z)V  

   60:  return  

  LineNumberTable:  

   line 20: 0  

   line 21: 6  

   line 22: 12  

   line 23: 19  

   line 24: 27  

   line 25: 43  

   line 26: 60  

  

  StackMapTable: number_of_entries = 4  

   frame_type = 255 /* full_frame */  

     offset_delta = 39  

     locals = [ class "[Ljava/lang/String;", class java/lang/Integer, class java/lang/Integer, class java/lang/Inte  

ger, class java/lang/Integer ]  

     stack = [ class java/io/PrintStream ]  

   frame_type = 255 /* full_frame */  

     offset_delta = 0  

     locals = [ class "[Ljava/lang/String;", class java/lang/Integer, class java/lang/Integer, class java/lang/Inte  

ger, class java/lang/Integer ]  

     stack = [ class java/io/PrintStream, int ]  

   frame_type = 79 /* same_locals_1_stack_item */  

     stack = [ class java/io/PrintStream ]  

   frame_type = 255 /* full_frame */  

     offset_delta = 0  

     locals = [ class "[Ljava/lang/String;", class java/lang/Integer, class java/lang/Integer, class java/lang/Inte  

ger, class java/lang/Integer ]  

     stack = [ class java/io/PrintStream, int ]  

  

  Exceptions:  

   throws java.lang.Exception  

}  

 Integer a = 127;对应的指令为:

Java代码  


0:   bipush  127  

2:   invokestatic    #2; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;  

Integer c = 128;对应的指令为:

Java代码  


12:  sipush  128  

15:  invokestatic    #2; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;  

     bipush指令意思是将单字节的常量值(-128~127)推送到栈顶

     sipush指令意思是将短型的常量值(-32768~32767)推送到栈顶

     invokestatic指令意思是调用静态方法,这里调用的是常量池中#2指向的方法java/lang/Integer.valueOf,查看Integer.valueOf方法:

Java代码  


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);  

}  

 IntegerCache代码:

Java代码  


   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);  

}  

   }  

     其它封装类如下:

Java代码  


//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   

 

 

     String s =  new  String( "xyz" );  在运行时涉及 几个String实例?

     两个,一个是字符串字面量"xyz"所对应的、驻留(intern)在一个全局共享的字符串常量池中的实例,另一个是通过new String(String)创建并初始化的、内容与"xyz"相同的实例。

 

     String中的final用法和理解:

     final只对引用的"值"(即内存地址)有效,它迫使引用只能指向初始指向的那个对象,改变它的指向会导致编译期错误。至于它所指向的对象的变化,final是不负责的。

Java代码  


final StringBuffer a = new StringBuffer("111");  

final StringBuffer b = new StringBuffer("222");  

a=b;//此句编译不通过   

  

final StringBuffer a = new StringBuffer("111");  

a.append("222");//编译通过    

 

Java代码  


String a = "a1";  

String b = "a" + 1;  

System.out.println((a == b)); //result = true  

String a = "atrue";  

String b = "a" + "true";  

System.out.println((a == b)); //result = true  

String a = "a3.4";  

String b = "a" + 3.4;  

System.out.println((a == b)); //result = true   

    JVM对于字符串常量的"+"号连接,将程序编译期,JVM就将常量字符串的"+"连接优化为连接后的值,拿"a" + 1来说,经编译器优化后在class中就已经是a1。在编译期其字符串常量的值就确定下来,故上面程序最终的结果都为true。

 

Java代码  


String a = "ab";  

String bb = "b";  

String b = "a" + bb;  

System.out.println((a == b)); //result = false   

   JVM对于字符串引用,由于在字符串的"+"连接中,有字符串引用存在,而引用的值在程序编译期是无法确定的,即"a" + bb无法被编译器优化,只有在程序运行期来动态分配并将连接后的新地址赋给b。所以上面程序的结果也就为false。

 

 

Java代码  


String a = "ab";  

final String bb = "b";  

String b = "a" + bb;  

System.out.println((a == b)); //result = true   

     和上面唯一不同的是bb字符串加了final修饰,对于final修饰的变量,它在编译时被解析为常量值的一个本地拷贝存储到自己的常量池中或嵌入到它的字节码流中。所以此时的"a" + bb和"a" + "b"效果是一样的。故上面程序的结果为true。

 

Java代码  


String a = "ab";  

final String bb = getBB();  

String b = "a" + bb;  

System.out.println((a == b)); //result = false  

  

private static String getBB() {  

    return "b";  

}  

 JVM对于字符串引用bb,它的值在编译期无法确定,只有在程序运行期调用方法后,将方法的返回值和"a"来动态连接并分配地址为b,故上面程序的结果为false。

 

通过上面4个例子可以得出得知:

 

Java代码  


String  s  =  "a" + "b" + "c";    

就等价于String s = "abc";  int i = 1+2+3;也等价于int i = 6;

 

Java代码  


String  a  =  "a";    

String  b  =  "b";    

String  c  =  "c";    

String  s  =   a  +  b  +  c;     

 这个就不一样了,最终结果等于:

Java代码  


StringBuffer temp = new StringBuffer();    

temp.append(a).append(b).append(c);    

String s = temp.toString();   

 由上面的分析结果,可就不难推断出String 采用连接运算符(+)效率低下原因分析,形如这样的代码:

Java代码  


public class Test {  

    public static void main(String args[]) {  

        String s = null;  

        for(int i = 0; i < 100; i++) {  

            s += "a";  

        }  

    }  

}   

     每做一次 + 就产生个StringBuilder对象,然后append后就扔掉。下次循环再到达时重新产生个StringBuilder对象,然后 append 字符串,如此循环直至结束。 如果我们直接采用 StringBuilder 对象进行 append 的话,我们可以节省 N - 1 次创建和销毁对象的时间。所以对于在循环中要进行字符串连接的应用,一般都是用StringBuffer或StringBulider对象来进行 append操作。

 

String.intern()解析

Java语言并不要求常量一定只能在编译期产生,运行时也可能将新的常量放入常量池中,这种特性用的最多的就是String.intern()方法。

String的intern()方法就是扩充常量池的一个方法;当一个String实例str调用intern()方法时,Java查找常量池中是否有相同Unicode的字符串常量,如果有,则返回其的引用,如果没有,则在常量池中增加 一个Unicode等于str的字符串并返回它的引用。

Java代码  


String s0= "xyz";  

String s1=new String("xyz");  

String s2=new String("xyz");  

  

System.out.println(s0==s1);  

s1.intern();  

s2=s2.intern(); //把常量池中“pku”的引用赋给s2  

System.out.println( s0==s1);  

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

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

 输出为: 

false 

false //虽然执行了s1.intern(),但它的返回值没有赋给s1 

true //说明s1.intern()返回的是常量池中”pku”的引用 

true

 

有人说,“使用String.intern()方法则可以将一个String类的保存到一个全局String表中,如果具有相同值的Unicode字符串 已经在这个表中,那么该方法返回表中已有字符串的地址,如果在表中没有相同值的字符串,则将自己的地址注册到表中“如果我把他说的这个全局的String 表理解为常量池的话,他的最后一句话,“如果在表中没有相同值的字符串,则将自己的地址注册到表中”是错的:

Java代码  


String s1=new String("xyz");  

String s2=s1.intern();  

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

System.out.println( s1+" "+s2 );  

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

输出为: 

false 

xyz xyz 

true

转载自:http://chenzehe.iteye.com/blog/1727062
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: