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

    2021-03-17 12:31:17
    长期以来,我们都知道“常量词”这个词,但是对这个概念未必有足够的理解,这篇文章就来从概念,从源码上深入理解下Java常量池。 概念介绍:  Java中的常量池通常指的是运行时常量池,是方法区中的一部分,一个JVM...

    长期以来,我们都知道“常量池”这个词,但是对这个概念未必有足够的理解,这篇文章就来从概念,从源码上深入理解下Java常量池。

    概念介绍:
      Java中的常量池通常指的是运行时常量池,是方法区中的一部分,一个JVM实例只有一个运行时常量池,各个线程共享该运行时常量池。
      Java常量池中保存的是一份在编译期间就已经确认的数据(静态常量数据,在类加载前就已经被实例化了)。包含final常量的值(包括成员常量、局部常量和引用常量)、以及对象字面量的值。
      在编译期间,每当给常量赋值它就会去检测常量池中是否存在该值,若存在直接返回该值的地址给常量,若不存在则先在常量池中创建该值,再返回该值的地址给常量。因此常量池中不可能出现相等的数据。

    1.final常量:
      一切经final关键字修饰的变量均为常量,final常量必须在定义时就赋初值,否则编译不通过。

    2.对象字面量:
      对象字面量是指直接以一常量给对象赋值,而不是在堆空间new出一个对象实例
      
    常见的两种对象字面量:
    (1)基本类型的包装类对象字面量
    (2)String对象字面量。

    2.1 基本类型的包装类对象字面量:
      Java八大基本类型对应的八个包装类大都实现了常量池技术,Byte,Short,Integer,Long,Boolean,Character这六种基本类型的包装类都实现了常量池,其中Character默认创建的了数值[0-127]的相应类型的缓存数据,其他五种则是[-128-127],但是超出此范围仍然会去创建新的对象。两个浮点类型的包装类Float和Double并没有实现常量池技术。

    public class Test{
    	
    	public static void main(String[] args){
    		   //5种整形的包装类Byte,Short,Integer,Long,Character的对象,
    
    		   //在值小于127时可以使用常量池
    		   Integer i1=127;
    		   Integer i2=127;
    		   System.out.println(i1==i2);//输出true
    
    		   //值大于127时,不会从常量池中取对象
    		   Integer i3=128;
    		   Integer i4=128;
    
    		   System.out.println(i3==i4);//输出false
    
    		   //Boolean类也实现了常量池技术
    		   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的常量池的取值范围正是(-128~127),而128不在常量词的范围内,所以i3和i4都是重新建了一个Integer对象,地址还是不相同的,所以i3==i4是false。

    附Long的常量池的源码:

    private static class LongCache {
                private LongCache(){}
                static final Long cache[] = new Long[-(-128) + 127 + 1];
                static {
                    for(int i = 0; i < cache.length; i++)
                        cache[i] = new Long(i - 128);
                }
    }
    

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

    String.intern()方法强制将字符串放入常量池中。

    public static void main(String[] args) {
      String s1 = new String("计算机");
      String s2 = s1.intern();
      String s3 = "计算机";
      System.out.println("s1 == s2? " + (s1 == s2));//false
      System.out.println("s3 == s2? " + (s3 == s2));//true
    }
    //String的intern()方法会查找在常量池中是否存在一份equal相等的字符串,如果有则返回该字符串的引用,如果没有则添加自己的字符串进入常量池。
    
    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
    
    }
    }
    

    注意:
      final常量必须在定义时就赋初值,但对象字面量可以先定义后赋值。

    展开全文
  • JAVA常量池

    2018-04-13 17:39:59
    java常量池技术 java中的常量池技术,是为了方便快捷地创建某些对象而出现的,当需要一个对象时,就可以从池中取一个出来(如果池中没有则创建一个),则在需要重复创建相等变量时节省了很多时间。常量池其实也就是...
    java常量池技术  
    java中的常量池技术,是为了方便快捷地创建某些对象而出现的,当需要一个对象时,就可以从池中取一个出来(如果池中没有则创建一个),则在需要重复创建相等变量时节省了很多时间。常量池其实也就是一个内存空间,常量池存在于方法区中。
    String类也是java中用得多的类,同样为了创建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 常量对象,没找到再创建新的。所以对一个字符串对象的任何修改,都会产生一个新的字符串对象,原来的依然存在,等待垃圾回收。
    代码:
    String a = “test”;
    String b = “test”;
    String b = b+"java";
    a,b同时指向常量池中的常量值"test",b=b+"java"之后,b原先指向一个常量,内容为"test”,通过对b进行+"java" 操作后,b之前所指向的那个值没有改变,但此时b不指向原来那个变量值了,而指向了另一个String变量,内容为”test java“。原来那个变量还存在于内存之中,只是b这个变量不再指向它了。
    八种基本类型的包装类和对象池 
     java中基本类型的包装类的大部分都实现了常量池技术,这些类是Byte,Short,Integer,Long,Character,Boolean,另外两种浮点数类型的包装类则没有实现。另外Byte,Short,Integer,Long,Character这5种整型的包装类也只是在对应值小于等于127时才可使用常量池,也即对象不负责创建和管理大于127的这些类的对象。   一些对应的测试代码:
    public class Test{ public static void main(String[] args){
    //5种整形的包装类Byte,Short,Integer,Long,Character的对象,
    //在值小于127时可以使用常量池
    Integer i1=127;
    Integer i2=127;
    System.out.println(i1==i2); //输出true
    //值大于127时,不会从常量池中取对象
    Integer i3=128;
    Integer i4=128;
    System.out.println(i3==i4); //输出false
    //Boolean类也实现了常量池技术
    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对象的代码补充
    public static Integer valueOf(int i) {
    final int offset = 128;
    if (i >= -128 && i <= 127) {
    return IntegerCache.cache[i + offset];
    }
    return new Integer(i);
    }
    当你直接给一个Integer对象一个int值的时候,其实它调用了valueOf方法,然后你赋的这个值很特别,是128,那么没有进行cache方法,相当于new了两个新对象。所以问题中定义a、b的两句代码就类似于:
    Integer a = new Integer(128);
    Integer b = new Integer(128);
    这个时候再问你,输出结果是什么?你就知道是false了。如果把这个数换成127,再执行:
    Integer a = 127;
    Integer b = 127;
    System.out.println(a == b);
    结果就是:true
    进行对象比较时最好还是使用equals,便于按照自己的目的进行控制。这里引出equals()和==,equals比较的是字符串字面值即比较内容,==比较引用。
    看一下IntegerCache这个类里面的内容
    private static class IntegerCache {
    private IntegerCache() {
    }
    static final Integer cache[] = new Integer[-(-128) + 127 + 1];
    static {
    for (int i = 0; i < cache.length; i++)
    cache[i] = new Integer(i - 128);
    }
    }
    由于cache[]在IntegerCache类中是静态数组,也就是只需要初始化一次,即static{......}部分,所以,如果Integer对象初始化时是-128~127的范围,就不需要再重新定义申请空间,都是同一个对象---在IntegerCache.cache中,这样可以在一定程度上提高效率。
    针对String方面的补充
    在同包同类下,引用自同一String对象.
    在同包不同类下,引用自同一String对象.
    在不同包不同类下,依然引用自同一String对象.
    在编译成.class时能够识别为同一字符串的,自动优化成常量,所以也引用自同一String对象.
    在运行时创建的字符串具有独立的内存地址,所以不引用自同一String对象.
    String的intern()方法会查找在常量池中是否存在一份equal相等的字符串,
    如果有则返回一个引用,没有则添加自己的字符串进入常量池,注意:只是字符串部分。
    所以这时会存在2份拷贝,常量池的部分被String类私有并管理,自己的那份按对象生命周期继续使用。
    返回字符串对象的规范化表示形式
    一个初始值为空的字符串池,它由类 String 私有地维护。
    当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(该对象由 equals(Object) 方法确定),则返回池中的字符串引用。否则,将此 String 对象添加到池中,并且返回此 String 对象的引用。
    它遵循对于任何两个字符串 s 和 t,当且仅当 s.equals(t) 为 true 时,s.intern() == t.intern() 才为 true。
    所有字面值字符串和字符串赋值常量表达式都是内部的。
    ------------------------------------代码演示补充-------------------------------------
    String s0= "java";
    String s1=new String("java");
    String s2=new String("java");
    s1.intern();
    s2=s2.intern(); //把常量池中"java"的引用赋给s2
    System.out.println( s0==s1);//false “ intern返回的引用没有引用变量接收~ s1.intern();等于废代码.”
    System.out.println( s0==s1.intern() );//true
    System.out.println( s0==s2 );//true
    ------------------------------------代码演示补充-------------------------------------
    String s1=new String("java");
    String s2=s1.intern();//s1 检查常量池,发现没有就拷贝自己的字符串进去
    //s2 引用该字符串常量池的地址
    System.out.println(s2 == s1);//false
    System.out.println( s2==s1.intern());//true
    System.out.println( s1==s1.intern());// false
    版权声明:好好学习,天天向上 https://blog.csdn.net/u014086926/article/details/52206251
    展开全文
  • java常量池

    2019-09-17 15:38:06
    java常量池是一个经久不衰的话题,也是面试官的最爱,题目花样百出。 理论 jvm虚拟内存分布:     程序计数器是jvm执行程序的流水线,存放一些跳转指令,这个太高深,小菜不懂。 本地方法栈是jvm...

    java常量池是一个经久不衰的话题,也是面试官的最爱,题目花样百出。
    理论
    jvm虚拟内存分布:


     
     

    程序计数器是jvm执行程序的流水线,存放一些跳转指令,这个太高深,小菜不懂。
    本地方法栈
    是jvm调用操作系统方法所使用的栈。
    虚拟机栈是jvm执行java代码所使用的栈。
    方法区
    存放了一些常量、静态变量、类信息等,可以理解成class文件在内存中的存放位置。
    虚拟机堆是jvm执行java代码所使用的堆。
    Java中的常量池,实际上分为两种形态:
    静态常量池运行时常量池
    所谓
    静态常量池,即.class文件中的常量池,class文件中的常量池不仅仅包含字符串(数字)字面量,还包含类、方法的信息,占用class文件绝大部分空间。
    运行时常量池,则是jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区*中,我们常说的常量池,就是指方法区中的运行时常量池。
    接下来我们引用一些网络上流行的常量池例子,然后借以讲解。

    String s1 = "Hello";
    String s2 = "Hello";
    String s3 = "Hel" + "lo";
    String s4 = "Hel" + new String("lo");
    String s5 = new String("Hello");
    String s6 = s5.intern();
    String s7 = "H";
    String s8 = "ello";
    String s9 = s7 + s8;
    

    System.out.println(s1 == s2); // true
    System.out.println(s1 == s3); // true
    System.out.println(s1 == s4); // false
    System.out.println(s1 == s9); // false
    System.out.println(s4 == s5); // false
    System.out.println(s1 == s6); // true

    首先说明一点,在java 中,直接使用==操作符,比较的是两个字符串的引用地址,并不是比较内容,比较内容请用String.equals()。
    s1 == s2这个非常好理解,s1、s2在赋值时,均使用的字符串字面量,说白话点,就是直接把字符串写死,在编译期间,这种字面量会直接放入class文件的常量池中,从而实现复用,载入运行时常量池后,s1、s2指向的是同一个内存地址,所以相等。
    s1 == s3这个地方有个坑,s3虽然是动态拼接出来的字符串,但是所有参与拼接的部分都是已知的字面量,在编译期间,这种拼接会被优化,编译器直接帮你拼好,因此String s3 = "Hel" + "lo";在class文件中被优化成String s3 = "Hello";,所以s1 == s3成立。
    s1 == s4当然不相等,s4虽然也是拼接出来的,但new String("lo")这部分不是已知字面量,是一个不可预料的部分,编译器不会优化,必须等到运行时才可以确定结果,结合字符串不变定理,鬼知道s4被分配到哪去了,所以地址肯定不同。配上一张简图理清思路:

     
     
    s1 == s9也不相等,道理差不多,虽然s7、s8在赋值的时候使用的字符串字面量,但是拼接成s9的时候,s7、s8作为两个变量,都是不可预料的,编译器毕竟是编译器,不可能当解释器用,所以不做优化,等到运行时,s7、s8拼接成的新字符串,在堆中地址不确定,不可能与方法区常量池中的s1地址相同。
     
     

    s4 == s5已经不用解释了,绝对不相等,二者都在堆中,但地址不同。
    s1 == s6这两个相等完全归功于intern方法,s5在堆中,内容为Hello ,intern方法会尝试将Hello字符串添加到常量池中,并返回其在常量池中的地址,因为常量池中已经有了Hello字符串,所以intern方法直接返回地址;而s1在编译期就已经指向常量池了,因此s1和s6指向同一地址,相等。
    至此,我们可以得出三个非常重要的结论:
    必须要关注编译期的行为,才能更好的理解常量池。
    运行时常量池中的常量,基本来源于各个class文件中的常量池。
    程序运行时,除非手动向常量池中添加常量(比如调用intern方法),否则jvm不会自动添加常量到常量池。
    以上所讲仅涉及字符串常量池,实际上还有整型常量池、浮点型常量池等等,但都大同小异,只不过数值类型的常量池不可以手动添加常量,程序启动时常量池中的常量就已经确定了,比如整型常量池中的常量范围:-128~127,只有这个范围的数字可以用到常量池。
    实践
    说了这么多理论,接下来让我们触摸一下真正的常量池。
    前文提到过,class文件中存在一个静态常量池,这个常量池是由编译器生成的,用来存储java源文件中的字面量(本文仅仅关注字面量),假设我们有如下java代码:

     

     String s = "hi";
    

    为了方便起见,就这么简单,没错!将代码编译成class文件后,用winhex打开二进制格式的class文件。如图:


     
     

    简单讲解一下class文件的结构,开头的4个字节是class文件魔数,用来标识这是一个class文件,说白话点就是文件头,既:CA FE BA BE。
    紧接着4个字节是java的版本号,这里的版本号是34,因为笔者是用jdk8编译的,版本号的高低和jdk版本的高低相对应,高版本可以兼容低版本,但低版本无法执行高版本。所以,如果哪天读者想知道别人的class文件是用什么jdk版本编译的,就可以看这4个字节。
    接下来就是常量池入口,入口处用2个字节标识常量池常量数量,本例中数值为00 1A,翻译成十进制是26,也就是有25个常量,其中第0个常量是特殊值,所以只有25个常量。
    常量池中存放了各种类型的常量,他们都有自己的类型,并且都有自己的存储规范,本文只关注字符串常量,字符串常量以01开头(1个字节),接着用2个字节记录字符串长度,然后就是字符串实际内容。本例中为:01 00 02 68 69。
    接下来再说说运行时常量池,由于运行时常量池在方法区中,我们可以通过jvm参数:-XX:PermSize、-XX:MaxPermSize来设置方法区大小,从而间接限制常量池大小。
    假设jvm启动参数为:-XX:PermSize=2M -XX:MaxPermSize=2M,然后运行如下代码:

    //保持引用,防止自动垃圾回收
    List<String> list = new ArrayList<String>();
    

    int i = 0;

    while(true){
    //通过intern方法向常量池中手动添加常量
    list.add(String.valueOf(i++).intern());
    }

    程序立刻会抛出:Exception in thread "main" java.lang.outOfMemoryError: PermGen space异常。PermGen space正是方法区,足以说明常量池在方法区中。
    在jdk8中,移除了方法区,转而用Metaspace区域替代,所以我们需要使用新的jvm参数:-XX:MaxMetaspaceSize=2M,依然运行如上代码,抛出:java.lang.OutOfMemoryError: Metaspace异常。同理说明运行时常量池是划分在Metaspace区域中。具体关于Metaspace区域的知识,请读者自行搜索。
    本文所有代码均在jdk7、jdk8下测试通过,其他版本jdk可能会略有差异,请读者自行探索。
    参考文献:《深入理解java虚拟机———jvm高级特性与最佳实践》



    链接:https://www.jianshu.com/p/538231f9e61c

    展开全文
  • JAVA 常量池

    2020-09-12 23:32:36
    Java中的常量池,实际上分为两种形态:静态常量池和运行时常量池。 所谓静态常量池,即*.class文件中的常量池,class文件中的常量池不仅仅包含字符串(数字)字面量,还包含类、方法的信息,占用class文件绝大部分...

    Java中的常量池,实际上分为两种形态:静态常量池运行时常量池

         所谓静态常量池,即*.class文件中的常量池,class文件中的常量池不仅仅包含字符串(数字)字面量,还包含类、方法的信息,占用class文件绝大部分空间。这种常量池主要用于存放两大类常量:字面量(Literal)和符号引用量(Symbolic References),

    字面量相当于Java语言层面常量的概念,如数值,文本字符串,声明为final的常量值等

    符号引用则属于编译原理方面的概念:

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

         而运行时常量池,则是jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池。

     必须要关注编译期的行为,才能更好的理解常量池。

     运行时常量池中的常量,基本来源于各个class文件中的常量池。

     程序运行时,除非手动向常量池中添加常量(比如调用intern方法),否则jvm不会自动添加常量到常量池。

    可以理解为

    String s1 = "123"; 

    String s2 = new String("456");

    s1,s2正在编译器就把123,456字符常量放在类的静态常量池中,虚拟机加载类的时候把静态常量池加载到内存方法区,所以程序运行的时候123,456这两个字符串对象都在运行时常量池中,然后程序执行时把123引用赋给s1,456引用给String构建对象并把新建的对象引用赋给s2

    String s1=new String("abc"); 为例

    会有两个String被创建,一个是你的Class被CLassLoader加载时,你的"abc"被作为常量读入,在constant  pool里创建了一个共享的"abc"  

      然后,当调用到new  String("abc")的时候,会在heap里创建这个new  String("abc"); 

    考虑类加载阶段和实际执行时。

    (1)类加载对一个类只会进行一次。"abc"在类加载时就已经创建并驻留了(如果该类被加载之前已经有"abc"字符串被驻留过则不需要重复创建用于驻留的"xyz"实例)。驻留的字符串是放在全局共享的字符串常量池中的。

    (2)在这段代码后续被运行的时候,"abc"字面量对应的String实例已经固定了,不会再被重复创建。所以这段代码将常量池中的对象复制一份放到heap中,并且把heap中的这个对象的引用交给s1持有。

    这条语句创建了2个对象。

    参考

    https://blog.csdn.net/songwenbinasdf/article/details/79421107

    https://blog.csdn.net/chenkaibsw/article/details/80848069

     

    展开全文
  • java 常量池

    2016-06-11 18:59:00
    java常量池技术  java中常量池技术说的通俗点就是java级别的缓存技术,方便快捷的创建一个对象。当需要一个对象时,从池中去获取(如果池中没有,就创建一个并放入池中),当下次需要相同变量的时候,不用重新创建...
  • Java 常量池

    2020-03-24 21:17:47
    class常量池: 运行时常量池: 字符串常量池: 字符串常量池是全局共享的。

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 12,772
精华内容 5,108
关键字:

java常量池

java 订阅