`
chenzehe
  • 浏览: 532326 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

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

JVM 
阅读更多

    常量池(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时才可使用对象池。

 

     看下面代码:

/**
 * 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查看生成的字节码:

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;对应的指令为:

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

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

   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方法:

	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代码:

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

     其它封装类如下:

//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是不负责的。

final StringBuffer a = new StringBuffer("111");
final StringBuffer b = new StringBuffer("222");
a=b;//此句编译不通过 

final StringBuffer a = new StringBuffer("111");
a.append("222");//编译通过  

 

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。

 

String a = "ab";
String bb = "b";
String b = "a" + bb;
System.out.println((a == b)); //result = false 

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

 

 

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。

 

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个例子可以得出得知:

 

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

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

 

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

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

StringBuffer temp = new StringBuffer();  
temp.append(a).append(b).append(c);  
String s = temp.toString(); 

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

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的字符串并返回它的引用。

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 表理解为常量池的话,他的最后一句话,“如果在表中没有相同值的字符串,则将自己的地址注册到表中”是错的:

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

 

分享到:
评论

相关推荐

    JVM常量池教程吐血整理干货.md

    JVM常量池 Class常量池(静态常量池) 运行时常量池 字符串常量池(全局常量池) 包装类型缓存池 JVM常量池 Jvm常量池分为: Class常量池(静态常量池) 运行时常量池 字符串常量池(全局常量池) 包装类型缓存池 Class常量...

    06-VIP-JVM调优实战及常量池详解(1)1

    1. 字符串的分配,和其他的对象分配一样,耗费高昂的时间与空间代价,作为最基础的数据类型,大量频繁的创建 2. JVM为了提高性能和减少内存开销,在实例化字符串

    06-VIP-JVM调优实战及常量池详解(预习)1

    1. 字符串的分配,和其他的对象分配一样,耗费高昂的时间与空间代价,作为最基础的数据类型,大量频繁的创建 2. JVM为了提高性能和减少内存开销,在实例化字符串

    JavaFocus::hammer: Java重点内容 博客文章 样例

    JavaFocus:hammer: Java学习重点 ...success和isSuccessequals 和 ==String为什么是不可变的字符串常量池为什么直接定义的字符串可以调用String对象的各种方法JDK6 和 JDK7 substring原理的改变字符串拼接的几种方式

    AIC的Java课程1-6章

     [*]了解Java内存机制:栈、堆、常量池等,理解垃圾回收机制。 第3章 面向过程(数组和方法) 4课时  理解如何声明数组、构造数组、初始化数组以及使用数组中的各个元素。  清楚数组作为...

    Big-Data-Interview:大数据面试知识点

    Big-Data-InterviewJava开发、大数据...封装、继承和多态Java语言数据类型Java的自动类型转换,强制类型转换String的不可变性、虚拟机的常量池中的String字符串、String.intern()的底层原理Java语言中的关键字:finalJa

    深入Java虚拟机(原书第2版).pdf【附光盘内容】

    6.3 特殊字符串 6.3.1 全限定名 6.3.2 简单名称 6.3.3 描述符 6.4 常量池 6.4.1 constant_utf8_info表 6.4.2 constant_integer_info表 6.4.3 constant_float_info表 6.4.4 constant_long_...

    java面试题

    int和Integer有什么区别? 答:int是java的原始数据类型,Integer是java为int提供的封装类... 答:基本数据类型8种:int、short、byte、long、float、double、char、boolean String不是基本数据类型,引用数据类型。 ...

    JAVA 范例大全 光盘 资源

    实例91 流标记分割和统计字符串 234 实例92 Java操作Excel文件 237 第11章 Java高级特性 245 实例93 自动装箱与拆箱 245 实例94 for/in循环 247 实例95 参数不确定(可变长参数) 249 实例96 方法改变(协变式...

    Java范例开发大全 (源程序)

     实例42 字符串索引越界异常(StringIndexOutBounds) 60  实例43 操作错误(UnsupportedOperationException) 60  4.2 运行时异常 61  实例44 找不到指定类时发生的异常(ClassNotFoundException) 62  ...

    java范例开发大全(pdf&源码)

    实例42 字符串索引越界异常(StringIndexOutBounds) 60 实例43 操作错误(UnsupportedOperationException) 60 4.2 运行时异常 61 实例44 找不到指定类时发生的异常(ClassNotFoundException) 62 实例45 请求的...

    java范例开发大全源代码

     实例42 字符串索引越界异常(StringIndexOutBounds) 60  实例43 操作错误(UnsupportedOperationException) 60  4.2 运行时异常 61  实例44 找不到指定类时发生的异常(ClassNotFoundException) 62 ...

    java范例开发大全

    实例42 字符串索引越界异常(StringIndexOutBounds) 60 实例43 操作错误(UnsupportedOperationException) 60 4.2 运行时异常 61 实例44 找不到指定类时发生的异常(ClassNotFoundException) 62 实例45 请求的...

    Java范例开发大全(全书源程序)

    实例42 字符串索引越界异常(StringIndexOutBounds) 60 实例43 操作错误(UnsupportedOperationException) 60 4.2 运行时异常 61 实例44 找不到指定类时发生的异常(ClassNotFoundException) 62 实例45 请求...

    超级有影响力霸气的Java面试题大全文档

     JAVA平台提供了两个类:String和StringBuffer,它们可以储存和操作字符串,即包含多个字符的字符数据。这个String类提供了数值不可改变的字符串。而这个StringBuffer类提供的字符串进行修改。当你知道字符数据要...

    java 面试题 总结

    JAVA平台提供了两个类:String和StringBuffer,它们可以储存和操作字符串,即包含多个字符的字符数据。这个String类提供了数值不可改变的字符串。而这个StringBuffer类提供的字符串进行修改。当你知道字符数据要改变...

Global site tag (gtag.js) - Google Analytics