精华内容
下载资源
问答
  • 常量池

    2020-07-17 00:11:45
    @Test public void test() { Long a = 128L; Long b = 128L; Long c = -128L; Long d = -128L; System.out.println(a==b);... System.out.println(c==d);...Byte、Short、Integer、Long的常量池范围:-128
    @Test
    	public void test() {
    		Long a = 128L;
    		Long b = 128L;
    		Long c = -128L;
    		Long d = -128L;
    		System.out.println(a==b);
    		System.out.println(c==d);
    	}
    

    输出

    false
    true
    

    在这段代码中体现了三个点:

    1. 自动装箱与拆箱
    2. 引用类型的赋值运算
    3. 常量池

    这里重点讲一下常量池
    Byte、Short、Integer、Long的常量池范围:-128~127;
    浮点类型Float、Double都没有常量池
    Boolean的常量池就是true与false
    Char的常量池范围:0~127;
    ***String的常量池:***(静态常量池和动态常量池)
    1.直接赋值:只会开辟一个堆内存空间,并且会自动保存到对象池中以供下次重复使用
    2.构造方法(new):会开辟两块堆内存空间,其中一块空间将成为垃圾,并且不会入池,但是用户可以通过intern()手动入池

    展开全文
  • 字符串常量池、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文件中存储的数据类型             

     

    类型名称数量
    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

    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
    來源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

     

    展开全文
  • 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() {}
        }
    
    展开全文
  • 在Java中,常量池的概念想必很多人都听说过。这也是面试中比较常考的题目之一。在Java有关的面试题中,一般习惯通过String的有关问题来考察面试者对于常量池的知识的理解,几道简单的String面试题难倒了无数的开发者...

    在Java中,常量池的概念想必很多人都听说过。这也是面试中比较常考的题目之一。在Java有关的面试题中,一般习惯通过String的有关问题来考察面试者对于常量池的知识的理解,几道简单的String面试题难倒了无数的开发者。所以说,常量池是Java体系中一个非常重要的概念。

    谈到常量池,在Java体系中,共用三种常量池。分别是字符串常量池Class常量池运行时常量池

    本文是《好好说说Java中的常量池》系列的第一篇,先来介绍一下到底什么是Class常量池。

    什么是Class文件

    Java代码的编译与反编译那些事儿中我们介绍过Java的编译和反编译的概念。我们知道,计算机只认识0和1,所以程序员写的代码都需要经过编译成0和1构成的二进制格式才能够让计算机运行。

    我们在《深入分析Java的编译原理》中提到过,为了让Java语言具有良好的跨平台能力,Java独具匠心的提供了一种可以在所有平台上都能使用的一种中间代码——字节码(ByteCode)。

    有了字节码,无论是哪种平台(如Windows、Linux等),只要安装了虚拟机,都可以直接运行字节码。

    同样,有了字节码,也解除了Java虚拟机和Java语言之间的耦合。这话可能很多人不理解,Java虚拟机不就是运行Java语言的么?这种解耦指的是什么?

    其实,目前Java虚拟机已经可以支持很多除Java语言以外的语言了,如Groovy、JRuby、Jython、Scala等。之所以可以支持,就是因为这些语言也可以被编译成字节码。而虚拟机并不关心字节码是有哪种语言编译而来的。

    Java语言中负责编译出字节码的编译器是一个命令是javac

    javac是收录于JDK中的Java语言编译器。该工具可以将后缀名为.java的源文件编译为后缀名为.class的可以运行于Java虚拟机的字节码。

    如,我们有以下简单的HelloWorld.java代码:

    public class HelloWorld {
        public static void main(String[] args) {
            String s = "Hollis";
        }
    }

    通过javac命令生成class文件:

    javac HelloWorld.java

    生成HelloWorld.class文件:

    如何使用16进制打开class文件:使用 vim test.class ,然后在交互模式下,输入:%!xxd 即可。

    可以看到,上面的文件就是Class文件,Class文件中包含了Java虚拟机指令集和符号表以及若干其他辅助信息。

    要想能够读懂上面的字节码,需要了解Class类文件的结构,由于这不是本文的重点,这里就不展开说明了。

    读者可以看到,HelloWorld.class文件中的前八个字母是cafe babe,这就是Class文件的魔数(Java中的”魔数”

    我们需要知道的是,在Class文件的4个字节的魔数后面的分别是4个字节的Class文件的版本号(第5、6个字节是次版本号,第7、8个字节是主版本号,我生成的Class文件的版本号是52,这时Java 8对应的版本。也就是说,这个版本的字节码,在JDK 1.8以下的版本中无法运行)在版本号后面的,就是Class常量池入口了。

    Class常量池

    Class常量池可以理解为是Class文件中的资源仓库。 Class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池(constant pool table),用于存放编译器生成的各种字面量(Literal)和符号引用(Symbolic References)。

    由于不同的Class文件中包含的常量的个数是不固定的,所以在Class文件的常量池入口处会设置两个字节的常量池容量计数器,记录了常量池中常量的个数。

    -w697

    当然,还有一种比较简单的查看Class文件中常量池的方法,那就是通过javap命令。对于以上的HelloWorld.class,可以通过

    javap -v  HelloWorld.class

    查看常量池内容如下:

    从上图中可以看到,反编译后的class文件常量池中共有16个常量。而Class文件中常量计数器的数值是0011,将该16进制数字转换成10进制的结果是17。

    原因是与Java的语言习惯不同,常量池计数器是从0开始而不是从1开始的,常量池的个数是10进制的17,这就代表了其中有16个常量,索引值范围为1-16。

    常量池中有什么

    介绍完了什么是Class常量池以及如何查看常量池,那么接下来我们就要深入分析一下,Class常量池中都有哪些内容。

    常量池中主要存放两大类常量:字面量(literal)和符号引用(symbolic references)。

    字面量

    前面说过,运行时常量池中主要保存的是字面量和符号引用,那么到底什么字面量?

    在计算机科学中,字面量(literal)是用于表达源代码中一个固定值的表示法(notation)。几乎所有计算机编程语言都具有对基本值的字面量表示,诸如:整数、浮点数以及字符串;而有很多也对布尔类型和字符类型的值也支持字面量表示;还有一些甚至对枚举类型的元素以及像数组、记录和对象等复合类型的值也支持字面量表示法。

    以上是关于计算机科学中关于字面量的解释,并不是很容易理解。说简单点,字面量就是指由字母、数字等构成的字符串或者数值。

    字面量只可以右值出现,所谓右值是指等号右边的值,如:int a=123这里的a为左值,123为右值。在这个例子中123就是字面量。

    int a = 123;
    String s = "hollis";

    上面的代码事例中,123和hollis都是字面量。

    本文开头的HelloWorld代码中,Hollis就是一个字面量。

    符号引用

    常量池中,除了字面量以外,还有符号引用,那么到底什么是符号引用呢。

    符号引用是编译原理中的概念,是相对于直接引用来说的。主要包括了以下三类常量: * 类和接口的全限定名 * 字段的名称和描述符 * 方法的名称和描述符

    这也就可以印证前面的常量池中还包含一些com/hollis/HelloWorldmain([Ljava/lang/String;)V等常量的原因了。

    Class常量池有什么用

    前面介绍了这么多,关于Class常量池是什么,怎么查看Class常量池以及Class常量池中保存了哪些东西。有一个关键的问题没有讲,那就是Class常量池到底有什么用。

    首先,可以明确的是,Class常量池是Class文件中的资源仓库,其中保存了各种常量。而这些常量都是开发者定义出来,需要在程序的运行期使用的。

    在《深入理解Java虚拟》中有这样的表述:

    Java代码在进行Javac编译的时候,并不像C和C++那样有“连接”这一步骤,而是在虚拟机加载Class文件的时候进行动态连接。也就是说,在Class文件中不会保存各个方法、字段的最终内存布局信息,因此这些字段、方法的符号引用不经过运行期转换的话无法得到真正的内存入口地址,也就无法直接被虚拟机使用。当虚拟机运行时,需要从常量池获得对应的符号引用,再在类创建时或运行时解析、翻译到具体的内存地址之中。关于类的创建和动态连接的内容,在虚拟机类加载过程时再进行详细讲解。

    前面这段话,看起来很绕,不是很容易理解。其实他的意思就是: Class是用来保存常量的一个媒介场所,并且是一个中间场所。在JVM真的运行时,需要把常量池中的常量加载到内存中。

    至于到底哪个阶段会做这件事情,以及Class常量池中的常量会以何种方式被加载到具体什么地方,会在本系列文章的后续内容中继续阐述。欢迎关注我的博客(http://www.hollischuang.com) 和公众号(Hollis),即可第一时间获得最新内容。

    另外,关于常量池中常量的存储形式,以及数据类型的表示方法本文中并未涉及,并不是说这部分知识点不重要,只是Class字节码的分析本就枯燥,作者不想在一篇文章中给读者灌输太多的理论上的内容。感兴趣的读者可以自行Google学习,如果真的有必要,我也可以单独写一篇文章再深入介绍。

    参考资料

    《深入理解java虚拟机》 《Java虚拟机原理图解》 1.2.2、Class文件中的常量池详解(上)

    展开全文
  • 常量池与运行时常量池

    千次阅读 2018-07-22 14:35:56
    * 转载自 [java虚拟机:运行时常量池](https://www.cnblogs.com/xiaotian15/p/6971353.html) ...Class文件中除了有类的版本、字段、方法、接口等描述等信息外,还有一项信息是常量池(Constant Pool Table),用于...
  • 最近在看JVM, 常量池, 运行时常量池,字符串常量池 这个看的有点懵. 整理一下. class常量池是在编译的时候每个class都有的. 在编译阶段,存放的是常量的符号引用。 [在class文件中.] 字符串常量池在每个VM中只有...
  • Integer常量池

    2021-03-10 16:58:08
    Integer对象有一个常量池,...但是当interger包装的数值不在常量池范围内,integer会重新new一个Integer对象,这个时候,使用”= =“进行比较就是false; public void testInteger() { Integer a = 1; Integer b = 1;
  • 在java的内存分配中,经常听到很多关于常量池的描述,我开始看的时候也是看的很模糊,网上五花八门的说法简直太多了,最后查阅各种资料,终于算是差不多理清了,很多网上说法都有问题,笔者尝试着来区分...
  • 文章目录1、引言2、Class常量池3、运行时常量池3.1 基本类型的包装类3.2 String.intern()4、字符串常量池4.1 直接用双引号创建字符串4.2 使用new关键字创建字符串4.3 组合4.4 总结 1、引言 2、Class常量池 当 ....
  • 1.字符串常量池(String Constant Pool): 1.1:字符串常量池在Java内存区域的哪个位置? 在JDK6.0及之前版本,字符串常量池是放在Perm Gen区(也就是方法区)中; 在JDK7.0版本,字符串常量池被移到了堆中了。至于为...
  • 常量池(运行时常量池 静态常量池

    千次阅读 多人点赞 2019-03-29 11:39:16
    深入浅出java常量池 理论 jvm虚拟内存分布: 程序计数器是jvm执行程序的流水线,存放一些跳转指令。 本地方法栈是jvm调用操作系统方法所使用的栈。 虚拟机栈是jvm执行java代码所使用的栈。 ...
  • 前言、 一、常量池结构分析、 1、常量池位置、 2、常量池结构、 3、常量池单个常量、 4、常量池单个常量 tag 标签、 二、常量池字节码文件分析、 0、常量池附加信息、
  • public static void main(String[] args){Integer i0 = 0;...//true Integer包装类实现了常量池技术,默认会生成一个-128~127范围的一个常量池,i1,i2都是从常量池获取的40,指针指向同一个地址Integer i3 ...
  • 作者:tracy_666链接:https://www.jianshu.com/p/55f65dac1b4bJVM常量池主要分为Class文件常量池、运行时常量池,全局字符串常量池,以及基本类型包装类对象常量池。0、Class文件常量池class文件是一组以字节为单位...
  • java常量池的概念

    2019-09-22 11:31:43
    java中的常量池 java为很多基本类型的包装类/字符串都建立常量池 常量池:相同的值只储存一份,节省内存,共享访问 ...常量池范围 Boolean:true false Byte,Character:\u000–\u007f(0-127) Short,Int,Long:-...
  • -128~127,只有这个范围的数字可以用到常量池。,有字符串常量池,可以把这理解为整数常量池。 Integer aa1 = 126; Integer bb1 = 126; System.out.println(aa1 == bb1); 这时是true 但超过这个范围,就要...
  • 转载自 好好说说Java中的常量池之Class常量池 在Java中,常量池的概念想必很多人都听说过。这也是面试中比较常考的题目之一。在Java有关的面试题中,一般习惯通过String的有关问题来考察面试者对于常量池的知识的...
  • Java常量池

    2020-05-29 15:03:11
    文章目录1 什么是常量2 常量池3 常量池的优点4 常量池分类详解4.1 字符串常量池4.2 class常量池4.3 运行时常量池 1 什么是常量 用 final 修饰的成员变量表示常量,值一旦给定就无法改变。 final 修饰的变量有3种:...
  • 在java的内存分配中,经常听到很多关于常量池的描述,我开始看的时候也是看的很模糊,网上五花八门的说法简直太多了,最后查阅各种资料,终于算是差不多理清了,很多网上说法都有问题,笔者尝试着来区分一下这几个...
  • JAVA常量池

    2018-04-13 17:39:59
    java常量池技术 java中的常量池技术,是为了方便快捷地创建某些对象而出现的,当需要一个对象时,就可以从池中取一个出来(如果池中没有则创建一个),则在需要重复创建相等变量时节省了很多时间。常量池其实也就是...
  • Python的常量池

    2019-12-09 08:53:04
    对于int类型的数据,Python中的常量池范围是[-5, 257)。 以如下两种示例进行说明: a = 256 b = int("256") print(id(a), id(b)) #1363708960 1363708960 a = 257 b = int("257") print(id(a), id(b)) #...
  • 常量池集合

    2021-08-02 18:34:54
    一点睛 常量池是 Class 文件中...常量池中常量的数量是不固定的,所以在常量池的入口需要放置一项 u2 类型的无符号数,代表常量池容量计数值(constant_pool_count)。与 Java 中语言习惯不一样的是,这个容量计数...
  • 小菜先拙劣的表达一下jvm虚拟内存分布: 程序计数器是jvm执行程序的流水线,存放一些跳转... 方法区存放了一些常量、静态变量、类信息等,可以理解成class文件在内存中的存放位置。 虚拟机堆是jvm执行jav...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 54,385
精华内容 21,754
关键字:

常量池范围