JVM(六)JVM常量池

1.常量池类型

Java中的常量池分为三种:

  • 类文件常量池(静态常量池)(The Constant Pool)
  • 运行时常量池 (The Run-Time Constant Pool)
  • String 常量池

在JDK1.7前,运行时常量池逻辑包含字符串常量池,存放在方法区,此时HotSpot对方法区的实现为永久代。
在JDK1.7中,字符串常量池被从方法区拿到了堆中,这里没有提到运行时常量池,也就是说字符串常量池被单独拿到堆中,运行时常量区还在方法区中。
在JDK1.8中,HotSpot移除了永久代用元空间(MetaSpace)代替,这时候字符串常量池还在堆中,运行时常量池还在方法区,只是方法区用元空间实现。


1.字符串常量池 -----存在于堆中

字符串常量池里的内容是在类加载完成,经过验证,准备阶段之后在堆中生成字符串对象实例,然后将该字符串对象实例的引用值存到string pool中。(string pool中存的是引用值,而不是具体实例对象,具体的实例对象时在堆中开辟的一块空间存放的。)在HotSpot Vm里实现的string pool功能的String Table类,它是一个哈希表,里面存的是驻留字符串(也就是我们用双引号括起来的)引用(而不是实例本身),也就是说在堆中的某些字符串实例被这个StringTable引用之后就被赋予了"驻留字符串"的身份。这个StringTable在每个HotSpot Vm的实例只有一份,被所有类共享。


2.Class常量池(静态常量池)----存在于class文件中

javap -verbose Test.class 可以查看class文件中的常量池。

class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池(constant pool table),用于存放编译器生成的各种字面量(Literal)和符号引用(Symbolic References)。字面量就是我们所说的常量概念,如文本字符串,被声明为final的常量值等。符号引用时一组符号来描述所引用的目标。符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。一般包括下面三类常量:

  • 类和接口的全限定名
  • 字段的名称和描述符
  • 方法的名称和描述符

常量池的每一项常量都是一个表,一共有11中各不相同的表结构数据,每个表开始的第一位都是一个字节的标志位(取值1-12),代表当前这个常量属于哪种常量类型。每种不同类型具有不同的结构。

class文件常量池和运行时常量池关系

当java文件被编译成class文件之后,也就是会生成class常量池。

方法区class文件信息包含内容

可以看到方法区里的class文件信息包括:魔数(用来确定一个文件能否被JVM接受)、版本号、常量池、类、父类、和接口数组、字段、方法等信息。

下面用一张图来表示常量池里存储的内容:


class常量池中存储的内容

3.运行时常量池 -----存在于元空间中

jvm在执行某个类的时候,必须经过加载连接初始化,而连接又分为验证、准备、解析三个阶段。而当类加载到内存后,jvm就会将class常量池中的内容放到运行时常量池中。由此可知,运行时常量吃也是每个类都有一个。class常量池中存的是字面量和符号引用,也就是说它存的并不是实例对象,而是对象的符号引用值,而经过解析之后,也就是把符号引用替换为直接引用,解析过程去查询字符串常量池,也就是我们上面所说的String Table,以保证运行时常量池所引用的字符串与字符串常量池中所引用的是一致的。

class文件常量池和运行时常量池的关系以及区别
class文件常量池存储的是当class文件被虚拟机加载进来后存放在方法区的一些字面量和符号引用。

运行时常量池是当class文件被加载完成后,java虚拟机会将class文件常量池的内容转移到运行时常量池里。在此过程中符号引用有一部分是会被转为直接引用。比如说类静态方法或私有方法,实例构造方法等。这是因为这些方法不能被重写其他版本。而其他一些方法是在方法被第一次调用的时候才会将符号引用转变为直接引用。

运行时常量池相对于class常量池另外一个重要特性是具备动态性。运行期间也可能将新的常量放入池中。这种特性被开发人员利用比较多的是String类的intern()方法。(JDK1.7之前)


4.8种基本类型的包装类和常量池

java中基本类型的包装类,大部分都实现了常量池技术
即Byte,Short,Integer,Long,Character,Boolean,两种浮点型包装类并没有实现常量池技术。
除Boolean外,5中包装类默认创建了数值【-128,127】的相应类型的缓存数据,但是超出此范围仍然会去创建新的对象。


//Integer 缓存代码 :
public static Integer valueOf(int i) {
        assert IntegerCache.high >= 127;
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }
 Integer i1 = 40;
  Integer i2 = 40;
  System.out.println(i1==i2);//输出TRUE

 Integer i1 = 400;
  Integer i2 = 400;
  System.out.println(i1==i2);//输出FALSE
例子
Integer  i1 = 40;
Integer  i2 = new Integer(40);
System.out.println(i1 == i2); //输出false

Integer i1 == 40;Java在编译的时候会直接将代码封装成Integer.valueOf(40);从而使用常量池技术。
Integer i2 == new Integer(40);这种情况下会创建新的对象。


  Integer i1 = 40;
  Integer i2 = 40;
  Integer i3 = 0;
  Integer i4 = new Integer(40);
  Integer i5 = new Integer(40);
  Integer i6 = new Integer(0);
  
  System.out.println("i1=i2   " + (i1 == i2));
  System.out.println("i1=i2+i3   " + (i1 == i2 + i3));
  System.out.println("i1=i4   " + (i1 == i4));
  System.out.println("i4=i5   " + (i4 == i5));
  System.out.println("i4=i5+i6   " + (i4 == i5 + i6));   
  System.out.println("40=i5+i6   " + (40 == i5 + i6));   


i1=i2   true
i1=i2+i3   true
i1=i4   false
i4=i5   false
i4=i5+i6   true
40=i5+i6   true

语句i4 == i5 + i6,因为+操作符不适用于Integer对象,首先会对i5 和 i6进行拆箱操作。

String类和常量池
String str1 = "abcd";
String str2 = new String("abcd");
System.out.println(str1 == str2);//false

这两种方式是有差别的,第一种方式是在常量池中获取对象,没有就加入。第二种方式时直接在堆内存空间创建一个新的对象。

连接表达式
1.只有使用双引号包含的文本创建的String对象间 使用 “+”, 产生的新对象才会被加入到字符串池中。
2.对于所有使用new方式创建(包括null)的 “+” 连接表达式,它所产生的新对象都不会被加入字符串池中。

String str1 = "str";
String str2 = "ing";

String str3 = "str" + "ing";
String str4 = str1 + str2;
System.out.println(str3 == str4);//false

String str5 = "string";
System.out.println(str3 == str5); //true
public static final String A = "ab"; // 常量A
public static final String B = "cd"; // 常量B
public static void main(String[] args) {
     String s = A + B;  // 将两个常量用+连接对s进行初始化 
     String t = "abcd";   
    System.out.println(s == t);//true
 }

A和B都是常量,值是固定的,它在类编译阶段就已经被替换了。也就是说String s = A + B 等同于String s = "ab"+"cd";


public static final String A; // 常量A
public static final String B;    // 常量B
static {   
     A = "ab";   
     B = "cd";   
 }   
 public static void main(String[] args) {   
    // 将两个常量用+连接对s进行初始化   
     String s = A + B;   
     String t = "abcd";   
     System.out.println(s == t);//false

A和B虽然被定义为常量,但是它们都没有马上被赋值。在运算出s的值之前,它们何时被赋予什么样的值,都无法确定。因此A和B在被赋值之前,性质是一个变量。那么s在编译器不能被确定。只能在运行时被创建。

String s1 = new String("xyz")创建了几个对象。
创建了2个对象。
类加载时,"xyz"引用会被放入字符串常量池,堆中会产生一个常量池引用的“xyz”实例对象。
在代码被运行时,new String("xyz")会在堆中产生一个s1实例对象。

public class Test {
 public static void main(String[] args) {   
      String hello = "Hello", lo = "lo";
      System.out.println((hello == "Hello") + " ");
      System.out.println((Other.hello == hello) + " ");
      System.out.println((other.Other.hello == hello) + " ");
      System.out.println((hello == ("Hel"+"lo")) + " ");
      System.out.println((hello == ("Hel"+lo)) + " ");
      System.out.println(hello == ("Hel"+lo).intern());
 }   
}
class Other { static String hello = "Hello"; }
package other;
public class Other { public static String hello = "Hello"; }

true true true true false true```
在同包同类下,引用自同一String对象.
在同包不同类下,引用自同一String对象.
在不同包不同类下,依然引用自同一String对象.
在编译成.class时能够识别为同一字符串的,自动优化成常量,引用自同一String对象.
在运行时创建的字符串具有独立的内存地址,所以不引用自同一String对象.

常量池的好处

常量池为了避免频繁的创建和销毁影响系统性能,实现了对象共享。
例如字符串常量池,在编译阶段就把所有字符串放到一个常量池中,节省内存空间,节省运行时间。

双等号 == 的含义

基本数据类型之间用双等号,比较的是他们的数值。
符合数据类型之间用双等号,比较的是他们在内存中存放的地址。


总结

字符串常量池:每个JVM中只有一份,存放字符串常量的引用值。(StringTable)。

class常量池(静态常量池):在编译时产生,每个class都有。存放是常量的字面量和符号引用。

运行时常量池:类加载完成之后,除了转存class文件常量池里的内容外,还将部分符号引用转变为直接引用。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342