精华内容
下载资源
问答
  • 写在前面:博主是一位普普通通的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

    展开全文
  • Java常量池理解与总结

    2020-12-22 15:38:02
     在Class文件结构中,头的4个字节用于存储魔数Magic Number,用于确定一个文件是否能被JVM接受,再接着4个字节用于存储版本号,前2个字节存储次版本号,后2个存储主版本号,再接着是用于存放常量的常量池,由于...
  • 在java的内存分配中,经常听到很多关于常量池的描述,我开始看的时候也是看的很模糊,网上五花八门的说法简直太多了,最后查阅各种资料,终于算是差不多理清了,很多网上说法都有问题,笔者尝试着来区分一下这几个...

            在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,以保证运行时常量池所引用的字符串与全局字符串池中所引用的是一致的。

    举个实例来说明一下:

    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文件中存储的数据类型

    类型名称数量
    u4magic1
    u2minor_version1
    u2major_version1
    u2constant_pool_count1
    cp_infoconstant_poolconstant_pool_count - 1
    u2access_flags1
    u2this_class1
    u2super_class1
    u2interfaces_count1
    u2interfacesinterfaces_count
    u2fields_count1
    field_infofieldsfields_count
    u2methods_count1
    method_infomethodsmethods_count
    u2attribute_count1
    attribute_infoattributesattributes_count

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

    在这里插入图片描述

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

    下面是原java代码

    [java] view plain copy

    public class TestInt {    
        private String str = "hello";    
        void printInt(){    
            System.out.println(65535);    
        }    
    }   
    


    经过反编译后获得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

    展开全文
  • 面试中经常问道的常量池优化技术,Java中的常量值技术是为了方便快捷的创建某些对象而出现的,当需要一个对象时,就可以从池中取一个出来(如果没有则创建一个,创建一个比引用一个要耗时),则在需要重复创建相等...
  • class常量池、字符串常量池和运行时常量池的区别

    千次阅读 多人点赞 2020-04-03 20:42:03
    文章目录一、概念1、Class 常量池(Class Constant Pool)1.1、常量池中数据项类型2、字符串池(String Pool、String Literal Pool)2.1、参考文章:3、运行时常量池(Runtime Constant Pool)4、总结二、方法区的...

    一、概念

    1、常量池(Constant Pool)

    常量池(Constant Pool),也叫 class 常量池(Class Constant Pool)。

    java文件被编译成 class文件,class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项就是常量池(Constant Pool),用于存放编译器生成的各种字面量( Literal )和 符号引用(Symbolic References)。

    在这里插入图片描述

    字面量( Literal )就是我们所说的常量概念,如文本字符串、被声明为final的常量值等。

    符号引用Symbolic References) 是一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。

    一般包括下面三类常量:

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

    描述符 是描述字段或方法的类型的字符串。
    在这里插入图片描述

    1.1、常量池中数据项类型

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

    序号常量池中数据项类型类型标志类型描述
    1CONSTANT_Utf81UTF-8 编码的Unicode字符串
    2CONSTANT_Integer3int 类型字面值
    3CONSTANT_Float4float 类型字面值
    4CONSTANT_Long5long 类型字面值
    5CONSTANT_Double6double 类型字面值
    6CONSTANT_Class7对一个类或接口的符号引用
    7CONSTANT_String8String 类型字面值
    8CONSTANT_Fieldref9对一个字段的符号引用
    9CONSTANT_Methodref10对一个类中声明的方法的符号引用
    10CONSTANT_InterfaceMethodref11对一个接口中声明的方法的符号引用
    11CONSTANT_NameAndType12对一个字段 或 方法的部分符号引用

    每种不同类型的常量类型具有不同的结构,具体的可以查看《深入理解java虚拟机》第六章的内容。

    2、String Pool(字符串池、字符串常量池)

    String Pool (字符串池),即 String Literal Pool , 又叫 全局字符串池字符串常量池

    是在类加载完成,经过验证,准备阶段之后 中生成字符串对象实例,然后 将该字符串对象实例的 引用值 存到 String Pool 中

    记住:String Pool 中存的是 引用值,而不是具体的实例对象,具体的实例对象是在堆中开辟的一块空间存放的。

    在 HotSpot VM 里实现的 String Pool 功能的是一个 StringTable 类,它是一个哈希表,里面存的是 驻留字符( 也就是用双引号括起来的部分)的 引用(而不是驻留字符串实例本身),也就是说在堆中的某些字符串实例被这个 StringTable 引用之后就等同被赋予了”驻留字符串”的身份。这个StringTable在每个 HotSpot VM 的实例只有一份,被所有的类共享。

    2.1、参考文章:

    Java字符串池(String Pool)深度解析

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

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

    jvm在执行某个类的时候,必须经过加载、连接、初始化,而连接又包括验证、准备、解析三个阶段。而当类加载到内存中后,jvm就会将 class常量池 中的内容存放到 运行时常量池 中,由此可知,运行时常量池 也是每个类都有一个。

    在上面我也说了,class常量池 中存的是字面量和符号引用,也就是说他们存的并不是对象的实例,而是对象的符号引用值。而经过解析(resolve)之后,也就是把符号引用替换为直接引用,解析的过程会去查询 字符串常量池 ,也就是我们上面所说的StringTable,以保证 运行时常量池所 引用的字符串与 字符串常量池 中所引用的是一致的。

    举个实例来说明一下:

    public class StrTest {
    
        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
        }
    }
    

    解析程序的内存分配过程:

    1. 首先,在堆中会有一个 abc 实例对象,全局 StringTable 中存放着 abc 的一个引用值。

    2. 然后,运行第二句的时候会生成两个实例,一个是 def 的实例对象,并且 StringTable中存储一个 def 的引用值,还有一个是new出来的一个 def 的实例对象 。

    3. 与上面那个是不同的实例,当在解析str3的时候查找StringTable,里面有 abc 的全局驻留字符串引用,所以str3的引用地址与之前的那个已存在的相同 。

    4. str4是在运行的时候调用 intern() 函数,返回StringTable中 def 的引用值,如果没有就将str2的引用值添加进去,在这里,StringTable中已经有了 def 的引用值了,所以返回上面在new str2的时候添加到StringTable中的 def 引用值,

    5. 最后str5在解析的时候就也是指向存在于StringTable中的 def 的引用值。

    这样析之后,下面三个打印的值就容易理解了。

    上面程序,

    1. 首先,经过编译之后,在该类的 class常量池 中存放一些符号引用;

    2. 然后类加载之后,将 class常量池 中存放的符号引用转存到 运行时常量池 中;

    3. 然后经过验证,准备阶段之后,在堆中生成驻留字符串的实例对象(也就是上例中str1所指向的”abc”实例对象),然后将这个对象的引用存到全局String Pool中,也就是StringTable中;

    4. 最后在解析阶段,要把运行时常量池中的符号引用替换成直接引用,那么就直接查询StringTable,保证StringTable里的引用值与运行时常量池中的引用值一致,大概整个过程就是这样了。

    4、总结

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

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

    面试题:

    1. 方法区里的 class文件信息 和 class文件常量池 是个什么关系。
    2. class文件常量池 和 运行时常量池 是什么关系。

    2.1、三者关系图:

    在这里插入图片描述

    2.2、方法区class文件信息

    img

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

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

    类型名称数量
    u4magic1
    u2minor_version1
    u2major_version1
    u2constant_pool_count1
    cp_infoconstant_poolconstant_pool_count - 1
    u2access_flags1
    u2this_class1
    u2super_class1
    u2interfaces_count1
    u2interfacesinterfaces_count
    u2fields_count1
    field_infofieldsfields_count
    u2methods_count1
    method_infomethodsmethods_count
    u2attribute_count1
    attribute_infoattributesattributes_count

    2.3、class常量池:

    在这里插入图片描述

    2.4、运行时常量池:

    下面是原 java代码 :

    public class TestInt {    
    	private  String str = "hello";	
       	void printInt(){
      		System.out.println(65535);
      	}  
    }
    

    编译后得到class文件,再javap -v TestInt.class,经过反编译后得到如下信息:

    img

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

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

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

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

    2.5、总结:

    方法区里存储着 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

    展开全文
  • 主要介绍了C#之CLR内存字符串常量池(string),对于学习和理解C#内存原理很有帮助,需要的朋友可以参考下
  • 主要介绍了Java String 字符串常量池解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
  • 主要介绍了Java中的字符串常量池详细介绍,JVM为了减少字符串对象的重复创建,其维护了一个特殊的内存,这段内存被成为字符串常量池或者字符串字面量池,需要的朋友可以参考下
  • JVM常量池详解:深入理解Class常量池、运行时常量池、字符串常量池一、Class常量池2.1字面量2.2符号引用二、运行时常量池三、字符串常量池3.1设计思想3.2设计原理3.3字符串常量池位置3.4三种字符串操作 一、Class...


    ❤️作者主页: 温文尔雅的清欢渡
    ❤️ 近期学习方向: 性能调优

    ❤️欢迎 点赞 👍 收藏 ⭐ 留言 📝 关注 ✌ 私聊我

    一、Class常量池

    Class常量池就像Class文件中的资源仓库。包含类的版本、字段、方法、接口等描述信息, 以及常量池。常量池用于存放编译期生成的各种字面量和符号引用等信息。

           int a = 1;
           String b = "boss";
    

    简单的讲,等号的左边就是符合引用,等号的右边就是字面量。

    1.1字面量

    字面量指的是由字母、数字等构成的字符串或者数值常量。
    字面量只可以右值出现,所谓右值是指等号右边的值,如:int a=1 这里的a为左值,1为右值。在这个例子中1就是字面量。

    1.2符号引用

    在这里插入图片描述

    符号引用是编译原理中的概念,是相对于直接引用来说的。主要包括了以下三类常量:
    1.类和接口的全限定名 ,比如Stud类常量池里的 com/stud是类的全限定名
    2.字段的名称和描述符 , 比如a,b就是字段名称,就是一种符号引用
    3.方法的名称和描述符 ,比如main和add是方法名称,()是一种UTF8格式的描述符

    这些常量池现在是静态信息,只有到运行时被加载到内存后,这些符号才有对应的内存地址信息,这些常量池一旦被装入内存就变成运行时常量池,对应的符号引用在程序加载或运行时会被转变为被加载到内存区域的代码的直接引用,也就是我们说的动态链接了。
    例如,add()这个符号引用在运行时就会被转变为add()方法具体代码在内存中的地址,主要通过对象头里的类型指针去转换直接引用。

    二、运行时常量池

    运行时常量池指的是符号引用在运行时被加载到内存的常量池。

    三、字符串常量池

    3.1设计思想

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

    为字符串开辟一个字符串常量池,类似于缓存区。
    当创建字符串常量时,首先查询字符串常量池是否存在该字符串 ,
    存在该字符串,返回引用实例;
    不存在,实例化该字符串并放入池中。

    3.2设计原理

    字符串常量池底层是hotspot的C++实现的,底层类似一个 HashTable, 保存的本质上是字符串对象的引用。

    3.3字符串常量池位置

    Jdk1.6及之前: 有永久代, 运行时常量池在永久代,运行时常量池包含字符串常量池
    Jdk1.7:有永久代,字符串常量池从永久代里的运行时常量池分离到堆里
    Jdk1.8及之后: 无永久代,运行时常量池在元空间,字符串常量池里依然在堆里

    3.4三种字符串操作

    (1)直接赋值字符串

     String phone = "xiaomi10"; // phone 指向常量池中的引用
    

    这种方式创建的字符串对象,只会在常量池中。
    因为有"xiaomi10"这个字面量,创建对象phone 的时候,JVM会先去常量池中通过equals(oject) 方法,判断是否有相同的对象
    如果有,则直接返回该对象在常量池中的引用;
    如果没有,则会在常量池中创建一个新对象,再返回引用。

    (2)new String();

     String phone = new String("xiaomi10"); // phone 指向堆内存中的对象引用
    

    这种方式会保证字符串常量池和堆中都有这个对象,没有就创建,最后返回堆内存中的对象引用。
    步骤大致如下: 因为有"xiaomi10"这个字面量,所以会先检查字符串常量池中是否存在字符串"xiaomi10"
    不存在,先在字符串常量池里创建一个字符串对象;再去内存中创建一个字符串对象"xiaomi10";
    存在的话,就直接去堆内存中创建一个字符串对象"xiaomi10";
    最后,将堆内存中的引用返回。
    在这里插入图片描述

    (3)intern();

      String phone1 = new String("xiaomi10");
      String phone2 = phone1.intern();
      System.out.println(phone1 == phone2); //false
    

    String中的intern方法是一个 native 的方法,当调用 intern方法时,
    如果池已经包含一个等于此String对象的字符串 (用equals(oject)方法确定),则返回池中的字符串。
    否则,将intern返回的引用指向当前字符串 s1。
    在这里插入图片描述

    四、八种基本数据类型的包装类和对象池

    java中基本数据类型的包装类的大部分都实现了常量池技术(对象池,存放在堆上),这些类是Byte,Short,Integer,Long,Character,Boolean,另外两种浮点数类型的包装类则没有实现。
    另外Byte,Short,Integer,Long,Character这5种整型的包装类也只是在对应值小于等于127且大于等于-128时才可使用对象池,也即对象不负责创建和管理超出对象池缓存的这些类的对象。因为一般这种比较小的数用到的概率相对较大。

       public static void main(String[] args) {
            Integer i1 = 127; //这种调用底层实际是执行的Integer.valueOf(127),里面用到了IntegerCache对象池
            Integer i2 = 127;
            System.out.println(i1 == i2);//输出true
    
            //值大于127时,不会从对象池中取对象
            Integer i3 = 128;
            Integer i4 = 128;
            System.out.println(i3 == i4);//输出false
    
            //用new关键词新生成对象不会使用对象池
            Integer i5 = new Integer(127);
            Integer i6 = new Integer(127);
            System.out.println(i5 == i6);//输出false
    
            //Boolean类也实现了对象池技术,存放了"true"
            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
        }
    

    Integer.valueOf()源码

        public static Integer valueOf(int i) {
            if (i >= IntegerCache.low && i <= IntegerCache.high)
                return IntegerCache.cache[i + (-IntegerCache.low)];//在范围内就直接从对象池里面拿对象
            return new Integer(i);//否则,new一个新对象
        }
    
    //下面的代码是Integer的对象池IntegerCache
    private static class IntegerCache {
            static final int low = -128;
            static final int high;
            static final Integer cache[];
    
            static {
                // high value may be configured by property
                int h = 127;
                String integerCacheHighPropValue =
                    sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
                if (integerCacheHighPropValue != null) {
                    try {
                        int i = parseInt(integerCacheHighPropValue);
                        i = Math.max(i, 127);
                        // Maximum array size is Integer.MAX_VALUE
                        h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                    } catch( NumberFormatException nfe) {
                        // If the property cannot be parsed into an int, ignore it.
                    }
                }
                high = h;
    
                cache = new Integer[(high - low) + 1];
                int j = low;
                for(int k = 0; k < cache.length; k++)
                    cache[k] = new Integer(j++);//new256个对象,放到对象池
    
                // range [-128, 127] must be interned (JLS7 5.1.7)
                assert IntegerCache.high >= 127;
            }
    
            private IntegerCache() {}
        }
    
    展开全文
  • 常量池(运行时常量池 静态常量池

    千次阅读 多人点赞 2019-03-29 11:39:16
    深入浅出java常量池 理论 jvm虚拟内存分布: 程序计数器是jvm执行程序的流水线,存放一些跳转指令。 本地方法栈是jvm调用操作系统方法所使用的栈。 虚拟机栈是jvm执行java代码所使用的栈。 ...
  • 常量池 是.class文件的常量池,也可以理解为一张表,虚拟机指令根据这张常量表找到要执行的类名,方法名,参数类型,字面量等信息 运行时常量池 常量池是*.class文件中的,当该类被加载,它的常量池信息就会放入...
  • 在java的内存分配中,经常听到很多关于常量池的描述,我开始看的时候也是看的很模糊,网上五花八门的说法简直太多了,最后查阅各种资料,终于算是差不多理清了,很多网上说法都有问题,笔者尝试着来区分...
  • 《Java面试题系列》:一个长知识又很有意思的专栏。...其中涉及比较多的便是常量池,本篇文章汇总一下JDK的运行时常量池、字符串常量池、静态常量池的功能及存储结构。 JVM运行时内存结构 在了解常量池之前我们.
  • 最近在看JVM, 常量池, 运行时常量池,字符串常量池 这个看的有点懵. 整理一下. class常量池是在编译的时候每个class都有的. 在编译阶段,存放的是常量的符号引用。 [在class文件中.] 字符串常量池在每个VM中只有...
  • 下面小编就为大家分享一篇java String源码和String常量池的全面解析,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
  • 字符串常量池、class常量池和运行时常量池

    万次阅读 多人点赞 2017-06-12 20:09:36
    在java的内存分配中,经常听到很多关于常量池的描述,我开始看的时候也是看的很模糊,网上五花八门的说法简直太多了,最后查阅各种资料,终于算是差不多理清了,很多网上说法都有问题,笔者尝试着...
  • 在Java中,常量池的概念想必很多人都听说过。这也是面试中比较常考的题目之一。在Java有关的面试题中,一般习惯通过String的有关问题来考察面试者对于常量池的知识的理解,几道简单的String面试题难倒了无数的开发者...
  • 主要介绍了探究Java常量本质及三种常量池(小结),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
  • 在java的内存分配中,经常听到很多关于常量池的描述,我开始看的时候也是看的很模糊,网上五花八门的说法简直太多了,最后查阅各种资料,终于算是差不多理清了,很多网上说法都有问题,笔者尝试着来区分一下这几个...
  • 这段代码的执行结果如下: double: 是否有常量池:false true float: 是否有常量池:false true char: 是否有常量池:true true false true byte: 是否有常量池:true true false true boolean: 是否有常量池:true...
  • 什么是常量池?2.为什么需要常量池? 1.什么是常量池? 1.字节码文件中有个constant pool,就是常量池 2.当字节码文件被加载到内存中之后,方法区中会存放字节码文件的constant pool相关信息,这时候就成为了运行时...
  • JVM常量池浅析

    2019-10-27 16:49:47
    需要说明的一点是,这篇文章是以《深入理解Java虚拟机》第二...  主要分为:Class文件常量池、运行时常量池,当然还有全局字符串常量池,以及基本类型包装类对象常量池 1.Class文件常量池   阅读过《深入理解J...
  • 文章目录运行时常量池和字符串常量池存储内容存储位置常量池区别字符串常量池如何存储数据字符串常量池简介字符串常量池案例分析案例分析一分析二分析三分析四分析五分析六分析七String的Intern方法详解intern的作用...
  • 常量池与运行时常量池

    千次阅读 2018-07-22 14:35:56
    * 转载自 [java虚拟机:运行时常量池](https://www.cnblogs.com/xiaotian15/p/6971353.html) ...Class文件中除了有类的版本、字段、方法、接口等描述等信息外,还有一项信息是常量池(Constant Pool Table),用于...
  • 首先明确class常量池、运行时常量池、字符串常量池不是同一个概念。 常量池表(Constant Pool Table) 常量池表(Constant Pool Table)是Class文件(字节码文件)的一部分,在编译阶段,用于存放编译期生成的各种...
  • 方法区与Java堆一样,是各个线程共享的内存区域,它用于存储已经被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。 他也有一个别名为"非堆",目的是与Java堆区分开来。 方法区和永久代 ...
  • 小菜先拙劣的表达一下jvm虚拟内存分布: 程序计数器是jvm执行程序的流水线,存放一些跳转... 方法区存放了一些常量、静态变量、类信息等,可以理解成class文件在内存中的存放位置。 虚拟机堆是jvm执行jav...
  • JVM常量池详解

    多人点赞 2021-04-13 10:43:50
    提示:这里咱们要说的常量池常量池就是咱们面试中所说的常量池,谈谈你对常量池的认识?面试官一问咱们就懵逼了,你要记得你脑子中有一张图!!! 剩下的就好办了 提示:请各位大佬批评指正!! 文章目录JVM系列...
  • 在之前在看jvm虚拟机的书,结果看到常量池的时候,看得一脸懵逼,去网上查也是云里雾里.所以这里自己花几天摸清楚后,在这里做个笔记 因为字符串常量池现在网上争议颇多,官方文档也说得很含糊,以下几点并不是很明确: ...
  • 一、字符串常量池(String Pool)——位于方法区 1.结构: 它是一个String Table类,实质上是一个Hash表,默认长度是1009。全局字符串池里的内容是在类加载完成,经过验证,准备阶段之后在堆中生成字符串对象实例...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 165,019
精华内容 66,007
关键字:

常量池