迄今为止看到的对常量池和字符串最为透彻的解释,赞一个!

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

看下面代码:

  1. /**
  2. * Huisou.com Inc.
  3. * Copyright (c) 2011-2012 All Rights Reserved.
  4. */
  5. /**
  6. * @description
  7. * @package
  8. * @title Test.java
  9. * @author chenzehe
  10. * @email
  11. * @version
  12. * @updateUser
  13. * @create 2011-12-21 上午11:27:48
  14. * @update 2011-12-21 上午11:27:48
  15. */
  16. public class Test {
  17. public static void main(String[] args) throws Exception {
  18. ;
  19. ;
  20. ;
  21. ;
  22. System.out.println(a == b);// 输出true
  23. System.out.println(c == d);// 输出false
  24. }
  25. }

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

  1. E:\chenzehe\workspace_b2b_3th\test\src>javap -verbose Test
  2. Compiled from "Test.java"
  3. public class Test extends java.lang.Object
  4. SourceFile: "Test.java"
  5. Constant pool:
  6. = Method       #6.#21; //  java/lang/Object."<init>":()V
  7. = Method       #22.#23;        //  java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
  8. = Field        #24.#25;        //  java/lang/System.out:Ljava/io/PrintStream;
  9. = Method       #26.#27;        //  java/io/PrintStream.println:(Z)V
  10. = class        #28;    //  Test
  11. = class        #29;    //  java/lang/Object
  12. = Asciz        <init>;
  13. = Asciz        ()V;
  14. = Asciz        Code;
  15. = Asciz       LineNumberTable;
  16. = Asciz       main;
  17. = Asciz       ([Ljava/lang/String;)V;
  18. = Asciz       StackMapTable;
  19. = class       #30;    //  "[Ljava/lang/String;"
  20. = class       #31;    //  java/lang/Integer
  21. = class       #32;    //  java/io/PrintStream
  22. = Asciz       Exceptions;
  23. = class       #33;    //  java/lang/Exception
  24. = Asciz       SourceFile;
  25. = Asciz       Test.java;
  26. = NameAndType #7:#8;//  "<init>":()V
  27. = class       #31;    //  java/lang/Integer
  28. = NameAndType #34:#35;//  valueOf:(I)Ljava/lang/Integer;
  29. = class       #36;    //  java/lang/System
  30. = NameAndType #37:#38;//  out:Ljava/io/PrintStream;
  31. = class       #32;    //  java/io/PrintStream
  32. = NameAndType #39:#40;//  println:(Z)V
  33. = Asciz       Test;
  34. = Asciz       java/lang/Object;
  35. = Asciz       [Ljava/lang/String;;
  36. = Asciz       java/lang/Integer;
  37. = Asciz       java/io/PrintStream;
  38. = Asciz       java/lang/Exception;
  39. = Asciz       valueOf;
  40. = Asciz       (I)Ljava/lang/Integer;;
  41. = Asciz       java/lang/System;
  42. = Asciz       out;
  43. = Asciz       Ljava/io/PrintStream;;
  44. = Asciz       println;
  45. = Asciz       (Z)V;
  46. {
  47. public Test();
  48. Code:
  49. , Locals=1, Args_size=1
  50. :   aload_0
  51. :   invokespecial   #1; //Method java/lang/Object."<init>":()V
  52. :   return
  53. LineNumberTable:
  54. : 0
  55. public static void main(java.lang.String[])   throws java.lang.Exception;
  56. Code:
  57. , Locals=5, Args_size=1
  58. :   bipush  127
  59. :   invokestatic    #2; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
  60. :   astore_1
  61. :   bipush  127
  62. :   invokestatic    #2; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
  63. :  astore_2
  64. :  sipush  128
  65. :  invokestatic    #2; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
  66. :  astore_3
  67. :  sipush  128
  68. :  invokestatic    #2; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
  69. :  astore  4
  70. :  getstatic       #3; //Field java/lang/System.out:Ljava/io/PrintStream;
  71. :  aload_1
  72. :  aload_2
  73. :  if_acmpne       39
  74. :  iconst_1
  75. :  goto    40
  76. :  iconst_0
  77. :  invokevirtual   #4; //Method java/io/PrintStream.println:(Z)V
  78. :  getstatic       #3; //Field java/lang/System.out:Ljava/io/PrintStream;
  79. :  aload_3
  80. :  aload   4
  81. :  if_acmpne       56
  82. :  iconst_1
  83. :  goto    57
  84. :  iconst_0
  85. :  invokevirtual   #4; //Method java/io/PrintStream.println:(Z)V
  86. :  return
  87. LineNumberTable:
  88. : 0
  89. : 6
  90. : 12
  91. : 19
  92. : 27
  93. : 43
  94. : 60
  95. /* full_frame */
  96. locals = [ class "[Ljava/lang/String;", class java/lang/Integer, class java/lang/Integer, class java/lang/Inte
  97. ger, class java/lang/Integer ]
  98. stack = [ class java/io/PrintStream ]
  99. /* full_frame */
  100. locals = [ class "[Ljava/lang/String;", class java/lang/Integer, class java/lang/Integer, class java/lang/Inte
  101. ger, class java/lang/Integer ]
  102. stack = [ class java/io/PrintStream, int ]
  103. /* same_locals_1_stack_item */
  104. stack = [ class java/io/PrintStream ]
  105. /* full_frame */
  106. locals = [ class "[Ljava/lang/String;", class java/lang/Integer, class java/lang/Integer, class java/lang/Inte
  107. ger, class java/lang/Integer ]
  108. stack = [ class java/io/PrintStream, int ]
  109. Exceptions:
  110. throws java.lang.Exception
  111. }

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

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

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

  1. :  sipush  128
  2. :  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方法:

  1. public static Integer valueOf(int i) {
  2. ;
  3. && i <= 127) { // must cache
  4. return IntegerCache.cache[i + offset];
  5. }
  6. return new Integer(i);
  7. }

IntegerCache代码:

  1. private static class IntegerCache {
  2. private IntegerCache(){}
  3. ) + 127 + 1];
  4. static {
  5. ; i < cache.length; i++)
  6. );
  7. }
  8. }

其它封装类如下:

  1. //Boolean类也实现了常量池技术
  2. Boolean bool1=true;
  3. Boolean bool2=true;
  4. System.out.println(bool1==bool2); //输出true
  5. //浮点类型的包装类没有实现常量池技术
  6. Double d1=1.0;
  7. Double d2=1.0;
  8. System.out.println(d1==d2); //输出false

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

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

String中的final用法和理解:

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

  1. final StringBuffer a = new StringBuffer("111");
  2. final StringBuffer b = new StringBuffer("222");
  3. a=b;//此句编译不通过
  4. final StringBuffer a = new StringBuffer("111");
  5. a.append("222");//编译通过
  1. String a = "a1";
  2. ;
  3. System.out.println((a == b)); //result = true
  4. String a = "atrue";
  5. String b = "a" + "true";
  6. System.out.println((a == b)); //result = true
  7. String a = "a3.4";
  8. String b = "a" + 3.4;
  9. System.out.println((a == b)); //result = true

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

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

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

  1. String a = "ab";
  2. final String bb = "b";
  3. String b = "a" + bb;
  4. System.out.println((a == b)); //result = true

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

  1. String a = "ab";
  2. final String bb = getBB();
  3. String b = "a" + bb;
  4. System.out.println((a == b)); //result = false
  5. private static String getBB() {
  6. return "b";
  7. }

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

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

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

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

  1. String  a  =  "a";
  2. String  b  =  "b";
  3. String  c  =  "c";
  4. String  s  =   a  +  b  +  c;

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

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

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

  1. public class Test {
  2. public static void main(String args[]) {
  3. String s = null;
  4. ; i < 100; i++) {
  5. s += "a";
  6. }
  7. }
  8. }

每做一次 + 就产生个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的字符串并返回它的引用。

  1. String s0= "xyz";
  2. String s1=new String("xyz");
  3. String s2=new String("xyz");
  4. System.out.println(s0==s1);
  5. s1.intern();
  6. s2=s2.intern(); //把常量池中“pku”的引用赋给s2
  7. System.out.println( s0==s1);
  8. System.out.println( s0==s1.intern() );
  9. System.out.println( s0==s2 );

输出为: 

false 

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

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

true

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

  1. String s1=new String("xyz");
  2. String s2=s1.intern();
  3. System.out.println( s1==s1.intern() );
  4. System.out.println( s1+" "+s2 );
  5. System.out.println( s2==s1.intern() );

输出为: 

false 

xyz xyz 

true

原文地址:http://chenzehe.iteye.com/blog/1727062

测试代码:

  String str1 = new StringBuilder("哈哈哈哈").append("呵呵呵呵").toString();

        System.out.println("1--" + (str1.intern() == str1));

        String str2 = new String("嘿嘿嘿嘿");

        System.out.println("2--" + (str2.intern() == str2));

        String str3 = new StringBuilder("啊啊啊啊").toString();

        System.out.println("3--" + (str3.intern() == str3));





JDK1.6运行结果:

1--false

2--false

3--false

JDK1.7运行结果:

1--true

2--false

3--false

本人已经凌乱,能解惑的欢迎留言。。。

终于搞明白了!请参考:http://www.importnew.com/14142.html

JVM常量池和八种基本数据及字符串的更多相关文章

  1. 超过1W字深度剖析JVM常量池(全网最详细最有深度)

    面试题:String a = "ab"; String b = "a" + "b"; a == b 是否相等 面试考察点 考察目的: 考察对 ...

  2. JVM常量池

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

  3. jvm常量池 vsv为什么1000 == 1000返回为False,而100 == 100会返回为True?

    在jvm规范中,每个类型都有自己的常量池.常量池是某类型所用常量的一个有序集合,包括直接常量(基本类型,String)和对其他类型.字段.方法的符号引用.之所以是符号引用而不是像c语言那样,编译时直接 ...

  4. JVM 常量池

    最近正好在研究这个问题,题主问题本身是有问题的,在JDK7中HotSpot的常量池是放在Java Heap中,并非题目中的native memory中.在JDK6中是放在Perm Space.题主可以 ...

  5. JVM 常量池、运行时常量池、字符串常量池

    常量池: 即class文件常量池,是class文件的一部分,用于保存编译时确定的数据. 保存的内容如下图: D:\java\test\out\production\test>javap -ver ...

  6. String放入运行时常量池的时机与String.intern()方法解惑

    运行时常量池概述 Java运行时常量池中主要存放两大类常量:字面量和符号引用.字面量比较接近于Java语言层面的常量概念,如文本字符串.声明为final的常量值等. 而符号引用则属于编译原理方面的概念 ...

  7. JVM中的常量池详解

    在Java的内存分配中,总共3种常量池: 转发链接:https://blog.csdn.net/zm13007310400/article/details/77534349 1.字符串常量池(Stri ...

  8. JVM中的常量池

    在Java的内存分配中,总共3种常量池: ref:https://blog.csdn.net/zm13007310400/article/details/77534349 1.字符串常量池(Strin ...

  9. 【JVM虚拟机】(5)---深入理解JVM-Class中常量池

    深入理解Class---常量池 一.概念 1.jvm生命周期 启动:当启动一个java程序时,一个jvm实例就诞生了,任何一个拥有main方法的class都可以作为jvm实例运行的起点. 运行:mai ...

随机推荐

  1. APIO dispatching

    题目描述 在一个忍者的帮派里,一些忍者们被选中派遣给顾客,然后依据自己的工作获取报偿.在这个帮派里,有一名忍者被称之为 Master.除了 Master以外,每名忍者都有且仅有一个上级.为保密,同时增 ...

  2. ●BZOJ 4237 稻草人

    题链: http://www.lydsy.com/JudgeOnline/problem.php?id=4237 题解: CDQ分治,单调栈 把所有点先按x从小到大排序,然后去CDQ分治y坐标. 在分 ...

  3. bzoj 5290: [Hnoi2018]道路

    Description Solution PJDP毁青春 注意到性质:到根的道路不超过 \(40\) 条 所以我们只关系一个点上面的道路的情况就行了 设 \(f[x][i][j]\) 表示一个点 \( ...

  4. hdu 5398 动态树LCT

    GCD Tree Time Limit: 5000/2500 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)Total Su ...

  5. Mianbot:基于向量匹配的情境式聊天机器人

    Mianbot是采用样板与检索式模型搭建的聊天机器人,目前有两种产生回覆的方式,专案仍在开发中:) 其一(左图)是以词向量进行短语分类,针对分类的目标模组实现特征抽取与记忆回覆功能,以进行多轮对话,匹 ...

  6. Cisco 交换机配置的基本命令

    1.不同的vlan 不同vlan需要路由 在路由的端口设置多个IP段 交换机模拟器实验六 2.查看端口名字 SWA#sh vlan    default  Active  F0/1, F0/2, F0 ...

  7. sqlserver批量更新数据

    update t_hr_teadept set rjkm=b.yjkmfrom t_hr_teadept a inner join t_tr_bzxx_km b on a.bzh=b.bzh wher ...

  8. LeeCode

    No1. Given an array of integers, return indices of the two numbers such that they add up to a specif ...

  9. Day 1 Python简单程序

    一.高级语言和低级语言   最初的计算机程序都是用0和1的序列表示的,程序员直接使用的是机器指令,无需翻译,从纸带打孔输入即可执行得到结果.后来为了方便记忆,就将用0.1序列表示的机器指令都用符号助记 ...

  10. python学习之路网络编程篇(第四篇)- 续

    Memcache简介 Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载.它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高动态.数据库驱动网站的速 ...