精华内容
参与话题
问答
  • 写在前面:博主是一位普普通通的19届二本大学生,平时最大的爱好就是听听歌,逛逛B站。博主很喜欢的一句话花开堪折直须折,莫待无花空折枝:博主的理解是头一次为人,就应该做自己想做的...常量池与Class常量池 2.运.

    写在前面:博主是一位普普通通的19届双非软工在读生,平时最大的爱好就是听听歌,逛逛B站。博主很喜欢的一句话花开堪折直须折,莫待无花空折枝:博主的理解是头一次为人,就应该做自己想做的事,做自己不后悔的事,做自己以后不会留有遗憾的事,做自己觉得有意义的事,不浪费这大好的青春年华。博主写博客目的是记录所学到的知识并方便自己复习,在记录知识的同时获得部分浏览量,得到更多人的认可,满足小小的成就感,同时在写博客的途中结交更多志同道合的朋友,让自己在技术的路上并不孤单。>本篇博客由于比较深入的写进JVM底层,所以如果有错误希望可以指出咱们共同讨论

    痛苦对我们来说,究竟意味着什么?司马迁在《报任安书》中一语道破,文王拘而演《周易》,仲尼厄而作《春秋》,屈原放逐乃赋《离骚》,左丘失明厥有《国语》。

    目录:
    1.常量池与Class常量池
    2.运行时常量池
           
    运行时常量池的简介
           
    方法区的Class文件信息,Class常量池和运行时常量池的三者关系
    3.字符串常量池
           
    字符串常量池的简介
           
    采用字面值的方式创建字符串对象
           
    采用new关键字新建一个字符串对象
           
    字符串池的优缺点
    4.字符串常量池和运行时常量池之间的藕断丝连
           
    常量池和字符串常量池的版本变化
           
    String.intern在JDK6和JDK7之后的区别(重难点)
           
    字符串常量池里存放的是引用还是字面量

    在这里插入图片描述

    1.常量池

    常量池,也叫 Class 常量池(常量池==Class常量池)。Java文件被编译成 Class文件,Class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项就是常量池,常量池是当Class文件被Java虚拟机加载进来后存放在方法区 各种字面量 (Literal)和 符号引用

    在Class文件结构中,最头的4个字节用于 存储魔数 (Magic Number),用于确定一个文件是否能被JVM接受,再接着4个字节用于 存储版本号,前2个字节存储次版本号,后2个存储主版本号,再接着是用于存放常量的常量池常量池主要用于存放两大类常量:字面量和符号引用量,字面量相当于Java语言层面常量的概念,如文本字符串,声明为final的常量值等,符号引用则属于编译原理方面的概念。如下

    在这里插入图片描述

    在这里插入图片描述

    2.运行时常量池

    2.1运行时常量池的简介

    运行时常量池是方法区的一部分。运行时常量池是当Class文件被加载到内存后,Java虚拟机会 将Class文件常量池里的内容转移到运行时常量池里(运行时常量池也是每个类都有一个)。运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生,也就是并非预置入Class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中

    2.2方法区的Class文件信息,Class常量池和运行时常量池的三者关系

    在这里插入图片描述

    字符串常量池

    3.1字符串常量池的简介

    字符串常量池又称为:字符串池,全局字符串池,英文也叫String Pool。 在工作中,String类是我们使用频率非常高的一种对象类型。JVM为了提升性能和减少内存开销,避免字符串的重复创建,其维护了一块特殊的内存空间,这就是我们今天要讨论的核心:字符串常量池。字符串常量池由String类私有的维护

    我们理清几个概念:

    在JDK7之前字符串常量池是在永久代里边的,但是在JDK7之后,把字符串常量池分进了堆里边。看下面两张图:

    在这里插入图片描述

    在堆中的字符串常量池: **堆里边的字符串常量池存放的是字符串的引用或者字符串(两者都有)**下面例子会有具体的讲解

    在这里插入图片描述

    符号引用表会在下面讲

    我们知道,在Java中有两种创建字符串对象的方式:

    1. 采用字面值的方式赋值
    2. 采用new关键字新建一个字符串对象。这两种方式在性能和内存占用方面存在着差别。

    3.2采用字面值的方式创建字符串对象

    package Oneday;
    public class a {
        public static void main(String[] args) {
            String str1="aaa";
            String str2="aaa";
            System.out.println(str1==str2);   
        }
    }
    运行结果:
    true
    

    采用字面值的方式创建一个字符串时,JVM首先会去字符串池中查找是否存在"aaa"这个对象,如果不存在,则在字符串池中创建"aaa"这个对象,然后将池中"aaa"这个对象的引用地址返回给字符串常量str,这样str会指向池中"aaa"这个字符串对象;如果存在,则不创建任何对象,直接将池中"aaa"这个对象的地址返回,赋给字符串常量。

    对于上述的例子:这是因为,创建字符串对象str2时,字符串池中已经存在"aaa"这个对象,直接把对象"aaa"的引用地址返回给str2,这样str2指向了池中"aaa"这个对象,也就是说str1和str2指向了同一个对象,因此语句System.out.println(str1== str2)输出:true

    3.3采用new关键字新建一个字符串对象

    package Oneday;
    public class a {
        public static void main(String[] args) {
            String str1=new String("aaa");
            String str2=new String("aaa");
            System.out.println(str1==str2);
        }
    }
    运行结果:
    false
    

    采用new关键字新建一个字符串对象时,JVM首先在字符串常量池中查找有没有"aaa"这个字符串对象,如果有,则不在池中再去创建"aaa"这个对象了,直接在堆中创建一个"aaa"字符串对象,然后将堆中的这个"aaa"对象的地址返回赋给引用str1,这样,str1就指向了堆中创建的这个"aaa"字符串对象;如果没有,则首先在字符串常量池池中创建一个"aaa"字符串对象,然后再在堆中创建一个"aaa"字符串对象,然后将堆中这个"aaa"字符串对象的地址返回赋给str1引用,这样,str1指向了堆中创建的这个"aaa"字符串对象。

    对于上述的例子:
    因为,采用new关键字创建对象时,每次new出来的都是一个新的对象,也即是说引用str1和str2指向的是两个不同的对象,因此语句
    System.out.println(str1 == str2)输出:false

    字符串池的实现有一个前提条件:String对象是不可变的。因为这样可以保证多个引用可以同时指向字符串池中的同一个对象。如果字符串是可变的,那么一个引用操作改变了对象的值,对其他引用会有影响,这样显然是不合理的。

    3.4字符串池的优缺点

    字符串池的优点就是避免了相同内容的字符串的创建,节省了内存,省去了创建相同字符串的时间,同时提升了性能;另一方面,字符串池的缺点就是牺牲了JVM在常量池中遍历对象所需要的时间,不过其时间成本相比而言比较低。

    4字符串常量池和运行时常量池之间的藕断丝连

    博主为啥要把他俩放在一起讲呢,主要是随着JDK的改朝换代,字符串常量池有很大的变动,和运行时常量池有关。而且网上众说纷纭,我真的在看的时候ctm了,所以博主花很长时间把这一块讲明白,如果有错误或者异议可以通知博主。博主一定会在第一时间参与讨论

    在这里插入图片描述

    4.1常量池和字符串常量池的版本变化

    • 在JDK1.7之前运行时常量池逻辑包含字符串常量池存放在方法区, 此时hotspot虚拟机对方法区的实现为永久代
    • 在JDK1.7 字符串常量池被从方法区拿到了堆中, 这里没有提到运行时常量池,也就是说 字符串常量池被单独拿到堆,运行时常量池剩下的东西还在方法区, 也就是hotspot中的永久代
    • 在JDK1.8 hotspot移除了永久代用元空间(Metaspace)取而代之, 这时候字符串常量池还在堆, 运行时常量池还在方法区, 只不过方法区的实现从永久代变成了元空间(Metaspace)

    4.2String.intern在JDK6和JDK7之后的区别(重点)

    JDK6和JDK7中该方法的功能是一致的,不同的是常量池位置的改变(JDK7将常量池放在了堆空间中),下面会具体说明。intern的方法返回字符串对象的规范表示形式。其中它做的事情是:首先去判断该字符串是否在常量池中存在,如果存在返回常量池中的字符串,如果在字符串常量池中不存在,先在字符串常量池中添加该字符串,然后返回引用地址

    例子1:

    String s1 = new String("1");
    s1.intern();
    String s2 = "1";
    System.out.println(s1 == s2);
    
    运行结果:
    JDK6运行结果:false
    JDK7运行结果:false
    

    我们首先看一张图:

    在这里插入图片描述上边例子中s1是new出来对象存放的位置的引用,s2是存放在字符串常量池的字符串的引用,所以两者不同

    例子2:

    String s1 = new String("1");
    System.out.println(s1.intern() == s1);
    
    运行结果:
    JDK6运行结果:false
    JDK7运行结果:false
    

    上边例子中s1是new出来对象存放的位置的引用,s1.intern()返回的是字符串常量池里字符串的引用

    例子3:

    String s1 = new String("1") + new String("1");
    s1.intern();
    String s2 = "11";
    System.out.println(s1 == s2);
    
    运行结果:
    JDK6运行结果:false
    JDK7运行结果:true
    

    JDK6中,s1.intern()运行时,首先去常量池查找,发现没有该常量,则在常量池中开辟空间存储"11",返回常量池中的值(注意这里也没有使用该返回值),第三行中,s2直接指向常量池里边的字符串,所以s1和s2不相等。有可能会有小伙伴问为啥s1.intern()发现没有该常量呢,那是因为:

    String s1 = new String(“1”) + new String(“1”);这行代码实际操作是,创建了一个StringBuilder对象,然后一路append,最后toString,而toString其实是又重新new了一个String对象,然后把对象给s1,此时并没有在字符串常量池中添加常量

    JDK7中,由于字符串常量池在堆空间中,所以在s1.intern()运行时,发现字符串 常量池没有常量,则添加堆中“11”对象的引用到字符串常量池,这个引用返回堆空间“11”地址(注意这里也没有使用该返回值),这时s2通过查找字符串常量池中的常量,查到的是s1.intern()存在字符串常量池里的“11”对象的引用,既然都是指向堆上的“11”对象,所以s1和s2相等。

    例子4:

    String s1 = new String("1") + new String("1");
    System.out.println(s1.intern() == s1);
    

    JDK6中,常量池在永久代中,s1.intern()去常量池中查找"11",发现没有该常量,则在常量池中开辟空间存储"11",返回常量池中的值,s1指向堆空间地址,所以二者不相等。

    JDK7中,常量池在堆空间,s1.intern()去常量池中查找"11",发现没有该常量,则在字符串常量池中开辟空间,指向堆空间地址,则返回字符串常量池指向的堆空间地址,s1也是堆空间地址,所以二者相等。

    另外美团的团队写了一篇关于intern()的博客,我觉得很好可以参考一下
    深入解析String#intern

    4.3字符串常量池里存放的是引用还是字面量

    我在例子3中讲了在JDK7中字符串常量池在堆上,仔细看看例3啥时候会放引用

    那么啥时候会放字面量在字符串常量池呢,那就是在我们new一个String对象的时候如果字符串常量池里边有字面量那么就不会放,如果字符串常量池没有就会放字面量。看一个例子:

    package Oneday;
    import java.util.HashSet;
    import java.util.Set;
    public class a {
        public static void main(String[] args) {
            String str1= new String("123");
            String str2=new String("123");
            System.out.println(str1==str2);
            System.out.println(str1.intern()==str2.intern());    
        }
    }
    

    运行结果:
    在这里插入图片描述
    首先 String str1= new String("123");会在堆中创建一个对象,返回这个对象的引用给str1,同时它还会在字符串常量池中检查有没有有没有123这个对象,如果没有就==再创建一个对象(也就是123这个字面量)==在字符串常量池中

    注意这里是创建了两个对象

    但是当我们字符串常量池里边有123这个对象,那么就不用继续创建了

    上面例子的false那是因为堆中的123对象不是同一个对象,但是第二个str1.intern和s2.intern指的都是字符串常量池里的123对象所以是true

    展开全文
  • 常量池怎么理解,常量池怎么理解,常量池怎么理解!常量池怎么理解,常量池怎么理解,常量池怎么理解,
  • 常量池

    2019-12-30 21:29:59
    常量池在java用于保存在编译器已确定的,已编译的class文件中的一份数据。它包括了关于类,方法,接口等中的常量,也包括字符串常量,如String s = "java" 这种申明方式;当然也可以扩充,故认为常量池是jvm的一块...

    jdk1.7之前放在方法区,1.7之后放在堆中。

     

    常量池在java用于保存在编译器已确定的,已编译的class文件中的一份数据。它包括了关于类,方法,接口等中的常量,也包括字符串常量,如String s = "java" 这种申明方式;当然也可以扩充,故认为常量池是jvm的一块特殊的内存空间。

     

    在Java程序中,有很多东西是永恒的,不会在运行过程中变化。比如一个类的名字,一个类字段的名字/所属类型,一个类方法的名字/返回类型/参数名与所属类型,一个常量,还有在程序中出现的大量的字面值。

    比如下面这段代码中蓝色部分:

    public class ClassTest {

        private String items = "我们";

        private final int iteml = 100;

        public void setItemS(String param) {...}

    }

     

    java常量池技术

    java中的常量池技术,是为了方便快捷地创建某些对象而出现的,当需要一个对象时,就可以从池中取一个出来(如果池中没有则创建一个),则在需要重复创建相等变量时节省了很多时间。常量池其实也就是一个内存空间。String类也实现了常量池的技术,同样的,大部分包装类也实现了常量池的技术,具体百度。以下用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

        }

    }

    用new String() 创建的字符串不是常量,不能再编译器就确定,所以new String() 创建的字符串不放入常量池中,他们有自己的地址空间。

     

    String对象的不变性机制会使修改String字符串时,产生大量的对象,因为每次改变字符串,都会生成一个新的String对象。java为了更有效的使用内存,常量池在编译器遇见String字符串时,它会检查该池内是否已经存在相同的字符串,如果找到,就把新变量的引用指向现有的字符串对象,不创建任何新的String常量对象,没找到则创建新的对象。所以对一个字符串对象的任何修改,都会产生一个新的字符串对象,原来的依然存在,等待垃圾回收。

    String a = "123456";
    String b = "123" + "456";
    String c = "123" + new String("456");
    System.out.println(a == b); // true
    System.out.println(b == c); // false

    我对于以上代码的理解:

    a先去常量池中找"123456",没找到,于是在常量池中创建了"123456"。而b的申明方式相当于"123"先于"456"拼接得到"123456",然后b去常量池中找"123456",找到了,于是b指向常量池中的"123456"。而c的申明方式,相当于在堆中创建了新对象,他不会去常量池中寻找,c会指向堆中其对应的内存空间。因此,a、b指向的是同一块空间,而b、c指向的是两块不同的空间。

     

    再说说String类中的intern()方法。

    String a = new String("java");
    String b = a.intern();
    String c = "java";
    System.out.println(a); // java
    System.out.println(b); // java
    System.out.println(a == b); // false
    System.out.println(a.intern() == b); // true
    System.out.println(c == b); // true
    System.out.println(c == a); // false

    intern()方法相当于在常量池中创建一个对象,然后将引用指向该对象。因此b、c指向的是常量池中的同一块地方。而a指向的则是堆中的一块内存。故出现以上结果。

     

    不要使用intern方法了,就是一个**方法。看以下的(jdk1.8):

    String str1 = new StringBuilder("计算机").append("软件").toString();
    System.out.println(str1.intern() == str1); // true
            
    String str3 = new StringBuilder("he").append("hehe").toString();
    System.out.println(str3.intern() == str3); // true
            
    String a = "jdk";
    String str4 = new StringBuilder("j").append("dk").toString();
    System.out.println(str4.intern() == str4); // false
            
    String str2 = new StringBuilder("ja").append("va").toString();
    System.out.println(str2.intern() == "java"); // true
    System.out.println(str2.intern() == str2); // false
    System.out.println(str2 == "java"); // false
            
    System.out.println("*****************");
            
    String str5 = new String("heihei"); 
    System.out.println(str5.intern() == str5); // false
    System.out.println(str5.intern() == "heihei"); // true
            
    String str6 = "heihei";
    System.out.println(str5 == str6); // false
    System.out.println(str5.intern() == str6); // true
            
    String str7 = new String("java");
    System.out.println(str7.intern() == str7); // false
    System.out.println(str7.intern() == "java"); // true

    展开全文
  • 学习java需要深刻理解常量池吗?学习java需要深刻理解常量池吗?学习java需要深刻理解常量池吗?
  • 字符串常量池、class常量池和运行时常量池

    万次阅读 多人点赞 2017-06-12 20:09:36
    在java的内存分配中,经常听到很多关于常量池的描述,我开始看的时候也是看的很模糊,网上五花八门的说法简直太多了,最后查阅各种资料,终于算是差不多理清了,很多网上说法都有问题,笔者尝试着...

     

    原文链接:http://tangxman.github.io/2015/07/27/the-difference-of-java-string-pool/

                           

    在java的内存分配中,经常听到很多关于常量池的描述,我开始看的时候也是看的很模糊,网上五花八门的说法简直太多了,最后查阅各种资料,终于算是差不多理清了,很多网上说法都有问题,笔者尝试着来区分一下这几个概念。

    1.全局字符串池(string pool也有叫做string literal pool)

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

    2.class文件常量池(class constant pool)

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

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

    常量池的每一项常量都是一个表,一共有如下表所示的11种各不相同的表结构数据,这每个表开始的第一位都是一个字节的标志位(取值1-12),代表当前这个常量属于哪种常量类型。 常量池的项目类型每种不同类型的常量类型具有不同的结构,具体的结构本文就先不叙述了,本文着重区分这三个常量池的概念(读者若想深入了解每种常量类型的数据结构可以查看《深入理解java虚拟机》第六章的内容)。

    3.运行时常量池(runtime constant pool)

    java文件被编译成class文件之后,也就是会生成我上面所说的class常量池,那么运行时常量池又是什么时候产生的呢?

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

    举个实例来说明一下:

    public class HelloWorld {
        public static void main(String []args) {
    		String str1 = "abc"; 
    		String str2 = new String("def"); 
    		String str3 = "abc"; 
    		String str4 = str2.intern(); 
    		String str5 = "def"; 
    		System.out.println(str1 == str3);//true 
    		System.out.println(str2 == str4);//false 
    		System.out.println(str4 == str5);//true
        }
    }

    回到上面的那个程序,现在就很容易解释整个程序的内存分配过程了,首先,在堆中会有一个”abc”实例,全局StringTable中存放着”abc”的一个引用值,然后在运行第二句的时候会生成两个实例,一个是”def”的实例对象,并且StringTable中存储一个”def”的引用值,还有一个是new出来的一个”def”的实例对象,与上面那个是不同的实例,当在解析str3的时候查找StringTable,里面有”abc”的全局驻留字符串引用,所以str3的引用地址与之前的那个已存在的相同,str4是在运行的时候调用intern()函数,返回StringTable中”def”的引用值,如果没有就将str2的引用值添加进去,在这里,StringTable中已经有了”def”的引用值了,所以返回上面在new str2的时候添加到StringTable中的 “def”引用值,最后str5在解析的时候就也是指向存在于StringTable中的”def”的引用值,那么这样一分析之后,下面三个打印的值就容易理解了。上面程序的首先经过编译之后,在该类的class常量池中存放一些符号引用,然后类加载之后,将class常量池中存放的符号引用转存到运行时常量池中,然后经过验证,准备阶段之后,在堆中生成驻留字符串的实例对象(也就是上例中str1所指向的”abc”实例对象),然后将这个对象的引用存到全局String Pool中,也就是StringTable中,最后在解析阶段,要把运行时常量池中的符号引用替换成直接引用,那么就直接查询StringTable,保证StringTable里的引用值与运行时常量池中的引用值一致,大概整个过程就是这样了。

    总结

    • 1.全局常量池在每个VM中只有一份,存放的是字符串常量的引用值。
    • 2.class常量池是在编译的时候每个class都有的,在编译阶段,存放的是常量的符号引用。
    • 3.运行时常量池是在类加载完成之后,将每个class常量池中的符号引用值转存到运行时常量池中,也就是说,每个class都有一个运行时常量池,类在解析之后,将符号引用替换成直接引用,与全局常量池中的引用值保持一致。

     

    ================================

     

     

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

    最近一直被方法区里面存着什么东西困扰着?

           1.方法区里存class文件信息和class文件常量池是个什么关系。

            2.class文件常量池和运行时常量池是什么关系。        

            方法区存着类的信息,常量和静态变量,即类被编译后的数据。这个说法其实是没问题的,只是太笼统了。更加详细一点的说法是方法区里存放着类的版本,字段,方法,接口和常量池。常量池里存储着字面量和符号引用。

           符号引用包括:1.类的全限定名,2.字段名和属性,3.方法名和属性。

           下面一张图是我画的方法区,class文件信息,class文件常量池和运行时常量池的关系

           

           下面一张图用来表示方法区class文件信息包括哪些内容:

           

             可以看到在方法区里的class文件信息包括:魔数,版本号,常量池,类,父类和接口数组,字段,方法等信息,其实类里面又包括字段和方法的信息。

             下面的图表是class文件中存储的数据类型             

     

    类型 名称 数量
    u4 magic 1
    u2 minor_version 1
    u2 major_version 1
    u2 constant_pool_count 1
    cp_info constant_pool constant_pool_count - 1
    u2 access_flags 1
    u2 this_class 1
    u2 super_class 1
    u2 interfaces_count 1
    u2 interfaces interfaces_count
    u2 fields_count 1
    field_info fields fields_count
    u2 methods_count 1
    method_info methods methods_count
    u2 attribute_count 1
    attribute_info attributes attributes_count

     

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

            这里写图片描述

     

    用一个class文件实际反编译一下

    下面是原java代码

     

    [java] view plain copy

    1. public class TestInt {    
    2.     private String str = "hello";    
    3.     void printInt(){    
    4.         System.out.println(65535);    
    5.     }    
    6. }   


    经过反编译后获得class文件是下面这样的

     

    可以看出被反编译的class文件中的内容和上面所说的是能对应上的。这就解答了class文件和class文件常量池的关系

    class文件常量池和运行时常量池的关系以及区别

    class文件常量池存储的是当class文件被java虚拟机加载进来后存放在方法区的一些字面量和符号引用,字面量包括字符串,基本类型的常量。

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

    总结:

    方法区里存储着class文件的信息和运行时常量池,class文件的信息包括类信息和class文件常量池。

    运行时常量池里的内容除了是class文件常量池里的内容外,还将class文件常量池里的符号引用转变为直接引用,而且运行时常量池里的内容是能动态添加的。例如调用String的intern方法就能将string的值添加到String常量池中,这里String常量池是包含在运行时常量池里的,但在jdk1.8后,将String常量池放到了堆中。

    下面有一篇文章写的是比较好的

    http://blog.csdn.net/vegetable_bird_001/article/details/51278339
     

    https://www.cnblogs.com/holos/p/6603379.html

    =====================================

    基本类型的包装类、String类和常量池

     

    一.相关概念


    1. 什么是常量
      用final修饰的成员变量表示常量,值一旦给定就无法改变!
      final修饰的变量有三种:静态变量、实例变量和局部变量,分别表示三种类型的常量。

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

    • 类和接口的全限定名
    • 字段名称和描述符
    • 方法名称和描述符
    1. 方法区中的运行时常量池
      运行时常量池是方法区的一部分。
      CLass文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。
      运行时常量池相对于CLass文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生,也就是并非预置入CLass文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用比较多的就是String类的intern()方法。

    2. 常量池的好处
      常量池是为了避免频繁的创建和销毁对象而影响系统性能,其实现了对象的共享。
      例如字符串常量池,在编译阶段就把所有的字符串文字放到一个常量池中。
      (1)节省内存空间:常量池中所有相同的字符串常量被合并,只占用一个空间。
      (2)节省运行时间:比较字符串时,==比equals()快。对于两个引用变量,只用==判断引用是否相等,也就可以判断实际值是否相等。

    3. 双等号==的含义
      基本数据类型之间应用双等号,比较的是他们的数值。
      复合数据类型(类)之间应用双等号,比较的是他们在内存中的存放地址。

    二.8种基本类型的包装类和常量池


    1. java中基本类型的包装类的大部分都实现了常量池技术,
      即Byte,Short,Integer,Long,Character,Boolean;
    
      Integer i1 = 40;
      Integer i2 = 40;
      System.out.println(i1==i2);//输出TRUE
    

    这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 = 400;
      Integer i2 = 400;
      System.out.println(i1==i2);//输出false
    
    1. 两种浮点数类型的包装类Float,Double并没有实现常量池技术。
    
       Double i1=1.2;
       Double i2=1.2;
       System.out.println(i1==i2);//输出false
    
    1. 应用常量池的场景
      (1)Integer i1=40;Java在编译的时候会直接将代码封装成Integer i1=Integer.valueOf(40);,从而使用常量池中的对象。
      (2)Integer i1 = new Integer(40);这种情况下会创建新的对象。
    
      Integer i1 = 40;
      Integer i2 = new Integer(40);
      System.out.println(i1==i2);//输出false
    
    1. Integer比较更丰富的一个例子
    
      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进行自动拆箱操作,进行数值相加,即i4 == 40。然后Integer对象无法与数值进行直接比较,所以i4自动拆箱转为int值40,最终这条语句转为40 == 40进行数值比较。
    Java中的自动装箱与拆箱

     

    三.String类和常量池


    1. String对象创建方式
    
         String str1 = "abcd";
         String str2 = new String("abcd");
         System.out.println(str1==str2);//false
    

    这两种不同的创建方法是有差别的,第一种方式是在常量池中拿对象,第二种方式是直接在堆内存空间创建一个新的对象。
    只要使用new方法,便需要创建新的对象。

    1. 连接表达式 +
      (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
    

    java基础:字符串的拼接

    • 特例1
    
    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";   
        if (s == t) {   
             System.out.println("s等于t,它们是同一个对象");   
         } else {   
             System.out.println("s不等于t,它们不是同一个对象");   
         }   
     } 
    s等于t,它们是同一个对象
    

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

    • 特例2
    
    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";   
        if (s == t) {   
             System.out.println("s等于t,它们是同一个对象");   
         } else {   
             System.out.println("s不等于t,它们不是同一个对象");   
         }   
     } 
    s不等于t,它们不是同一个对象
    

    A和B虽然被定义为常量,但是它们都没有马上被赋值。在运算出s的值之前,他们何时被赋值,以及被赋予什么样的值,都是个变数。因此A和B在被赋值之前,性质类似于一个变量。那么s就不能在编译期被确定,而只能在运行时被创建了。

    1. String s1 = new String("xyz"); 创建了几个对象? 
      考虑类加载阶段和实际执行时。
      (1)类加载对一个类只会进行一次。"xyz"在类加载时就已经创建并驻留了(如果该类被加载之前已经有"xyz"字符串被驻留过则不需要重复创建用于驻留的"xyz"实例)。驻留的字符串是放在全局共享的字符串常量池中的。
      (2)在这段代码后续被运行的时候,"xyz"字面量对应的String实例已经固定了,不会再被重复创建。所以这段代码将常量池中的对象复制一份放到heap中,并且把heap中的这个对象的引用交给s1 持有。
      这条语句创建了2个对象。

    2. java.lang.String.intern()
      运行时常量池相对于CLass文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生,也就是并非预置入CLass文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用比较多的就是String类的intern()方法。
      String的intern()方法会查找在常量池中是否存在一份equal相等的字符串,如果有则返回该字符串的引用,如果没有则添加自己的字符串进入常量池。

    
    public static void main(String[] args) {    
          String s1 = new String("计算机");
          String s2 = s1.intern();
          String s3 = "计算机";
          System.out.println("s1 == s2? " + (s1 == s2));
          System.out.println("s3 == s2? " + (s3 == s2));
      }
    
    
    s1 == s2? false
    s3 == s2? true
    
    1. 字符串比较更丰富的一个例子
    
    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对象.
    
    
    -----
    [2015-08-26]



    作者:梦工厂
    链接:https://www.jianshu.com/p/c7f47de2ee80
    來源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

     

    展开全文
  • 这几天在看Java虚拟机方面的知识时,看到了有几种不同常量池的说法,然后我就去CSDN、博客园等上找资料,里面说的内容真是百花齐放,各自争艳,因此,我好好整理了一下,将我自认为对的理解写下来与大家共同探讨: ...

    简介:

    这几天在看Java虚拟机方面的知识时,看到了有几种不同常量池的说法,然后我就去CSDN、博客园等上找资料,里面说的内容真是百花齐放,各自争艳,因此,我好好整理了一下,将我自认为对的理解写下来与大家共同探讨:

    在Java的内存分配中,总共3种常量池:

    1.字符串常量池(String Constant Pool):

    1.1:字符串常量池在Java内存区域的哪个位置?

    • 在JDK6.0及之前版本,字符串常量池是放在Perm Gen区(也就是方法区)中;
    • 在JDK7.0版本,字符串常量池被移到了堆中了。至于为什么移到堆内,大概是由于方法区的内存空间太小了。

    1.2:字符串常量池是什么?

    • 在HotSpot VM里实现的string pool功能的是一个StringTable类,它是一个Hash表,默认值大小长度是1009;这个StringTable在每个HotSpot VM的实例只有一份,被所有的类共享。字符串常量由一个一个字符组成,放在了StringTable上。
    • 在JDK6.0中,StringTable的长度是固定的,长度就是1009,因此如果放入String Pool中的String非常多,就会造成hash冲突,导致链表过长,当调用String#intern()时会需要到链表上一个一个找,从而导致性能大幅度下降;
    • 在JDK7.0中,StringTable的长度可以通过参数指定:
    -XX:StringTableSize=66666

    1.3:字符串常量池里放的是什么?

    • 在JDK6.0及之前版本中,String Pool里放的都是字符串常量;
    • 在JDK7.0中,由于String#intern()发生了改变,因此String Pool中也可以存放放于堆内的字符串对象的引用。关于String在内存中的存储和String#intern()方法的说明,可以参考我的另外一篇博客:

    需要说明的是:字符串常量池中的字符串只存在一份!
    如:

    String s1 = "hello,world!";
    String s2 = "hello,world!";

    即执行完第一行代码后,常量池中已存在 “hello,world!”,那么 s2不会在常量池中申请新的空间,而是直接把已存在的字符串内存地址返回给s2。(这里具体的字符串如何分配就不细说了,可以看我的另一篇博客)

    2.class常量池(Class Constant Pool):

    2.1:class常量池简介:

    • 我们写的每一个Java类被编译后,就会形成一份class文件;class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池(constant pool table),用于存放编译器生成的各种字面量(Literal)和符号引用(Symbolic References);
    • 每个class文件都有一个class常量池。

    2.2:什么是字面量和符号引用:

    • 字面量包括:1.文本字符串 2.八种基本类型的值 3.被声明为final的常量等;
    • 符号引用包括:1.类和方法的全限定名 2.字段的名称和描述符 3.方法的名称和描述符。

    3.运行时常量池(Runtime Constant Pool):

    • 运行时常量池存在于内存中,也就是class常量池被加载到内存之后的版本,不同之处是:它的字面量可以动态的添加(String#intern()),符号引用可以被解析为直接引用
    • JVM在执行某个类的时候,必须经过加载、连接、初始化,而连接又包括验证、准备、解析三个阶段。而当类加载到内存中后,jvm就会将class常量池中的内容存放到运行时常量池中,由此可知,运行时常量池也是每个类都有一个。在解析阶段,会把符号引用替换为直接引用,解析的过程会去查询字符串常量池,也就是我们上面所说的StringTable,以保证运行时常量池所引用的字符串与字符串常量池中是一致的。
    展开全文
  • 在java的内存分配中,经常听到很多关于常量池的描述,我开始看的时候也是看的很模糊,网上五花八门的说法简直太多了,最后查阅各种资料,终于算是差不多理清了,很多网上说法都有问题,笔者尝试着来区分...

空空如也

1 2 3 4 5 ... 20
收藏数 16,315
精华内容 6,526
关键字:

常量池