常量池技术是个什么鬼

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/J080624/article/details/82317844

Java中的常量池技术,是为了方便快捷地创建(获取)某些对象而出现的。当需要一个对象时,就可以从池中取一个出来(如果池中没有则创建一个),则在需要重复创建相等变量时节省了很多时间。

【1】Integer的小坑

先看个示例:

  public static void main(String[] args) {

        Integer a = 10;
        Integer b = 10;
        Integer c = 200;
        Integer d = 200;
        System.out.println(a==b);//true
        System.out.println(c==d);//false

        Integer i1=new Integer(1);  
        Integer i2=new Integer(1);  
        //i1,i2分别位于堆中不同的内存空间  
       System.out.println(i1==i2);//输出false  
    }

为什么a==b true;c==d false?

看编译后的class是个什么鬼?

public static void main(String[] args) {
        Integer a = Integer.valueOf(10);
        Integer b = Integer.valueOf(10);
        Integer c = Integer.valueOf(200);
        Integer d = Integer.valueOf(200);
        System.out.println(a == b);
        System.out.println(c == d);
    }

Integer a=10;其实就是Integer.valueOf(10)

跟一下源码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);
 }
 // low 默认为-128  high 默认为127

即,如果变量 i 在-128-127之间,直接从IntegerCache类的cache[]成员中根据下标拿一个返回。否则,new一个对象返回!!这也就是为什么a==b true;c==d false


注意!!! IntegerCache是个什么鬼?

private static class IntegerCache {
        static final int low = -128;
        //high并没有赋初值,是可以配置的
        static final int high;
        static final Integer cache[];
        // 静态代码块,类加载过程中就会执行
        static {
            // high value may be configured by property
            int h = 127;
            //尝试读取配置的high属性
            String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
                //如果不为null,则计算值并赋给h
            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.
                }
            }
            // 把h的值赋给high
            high = h;
            // 创建缓存数组
            cache = new Integer[(high - low) + 1];
            int j = low;
            // 将范围的integer 对象依次放入缓存
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);

            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }
      //...
}

这货是Integer的静态内部类,里面有个一静态常量数组很关键static final Integer cache[];!!!


再用javap -verbose TestA.class命令看一下:

C:\Users\12746\Desktop>javap -verbose TestA.class
Classfile /C:/Users/12746/Desktop/TestA.class
  Last modified 2018-9-2; size 850 bytes
  MD5 checksum c203fb4da7b2a99caad3e1388a87bad9
  Compiled from "TestA.java"
public class com.test.classes.TestA
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#29         // java/lang/Object."<init>":()V
   #2 = Methodref          #30.#31        // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   #3 = Fieldref           #32.#33        // java/lang/System.out:Ljava/io/PrintStream;
   #4 = Methodref          #34.#35        // java/io/PrintStream.println:(Z)V
   #5 = Class              #36            // com/test/classes/TestA
   #6 = Class              #37            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lcom/test/classes/TestA;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               args
  #17 = Utf8               [Ljava/lang/String;
  #18 = Utf8               a
  #19 = Utf8               Ljava/lang/Integer;
  #20 = Utf8               b
  #21 = Utf8               c
  #22 = Utf8               d
  #23 = Utf8               StackMapTable
  #24 = Class              #17            // "[Ljava/lang/String;"
  #25 = Class              #38            // java/lang/Integer
  #26 = Class              #39            // java/io/PrintStream
  #27 = Utf8               SourceFile
  #28 = Utf8               TestA.java
  #29 = NameAndType        #7:#8          // "<init>":()V
  #30 = Class              #38            // java/lang/Integer
  #31 = NameAndType        #40:#41        // valueOf:(I)Ljava/lang/Integer;
  #32 = Class              #42            // java/lang/System
  #33 = NameAndType        #43:#44        // out:Ljava/io/PrintStream;
  #34 = Class              #39            // java/io/PrintStream
  #35 = NameAndType        #45:#46        // println:(Z)V
  #36 = Utf8               com/test/classes/TestA
  #37 = Utf8               java/lang/Object
  #38 = Utf8               java/lang/Integer
  #39 = Utf8               java/io/PrintStream
  #40 = Utf8               valueOf
  #41 = Utf8               (I)Ljava/lang/Integer;
  #42 = Utf8               java/lang/System
  #43 = Utf8               out
  #44 = Utf8               Ljava/io/PrintStream;
  #45 = Utf8               println
  #46 = Utf8               (Z)V
{
  public com.test.classes.TestA();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 6: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/test/classes/TestA;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=5, args_size=1
         0: bipush        10
         2: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
         5: astore_1
         6: bipush        10
         8: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        11: astore_2
        12: sipush        200
        15: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        18: astore_3
        19: sipush        200
        22: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        25: astore        4
        27: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
        30: aload_1
        31: aload_2
        32: if_acmpne     39
        35: iconst_1
        36: goto          40
        39: iconst_0
        40: invokevirtual #4                  // Method java/io/PrintStream.println:(Z)V
        43: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
        46: aload_3
        47: aload         4
        49: if_acmpne     56
        52: iconst_1
        53: goto          57
        56: iconst_0
        57: invokevirtual #4                  // Method java/io/PrintStream.println:(Z)V
        60: return
      LineNumberTable:
        line 10: 0
        line 11: 6
        line 12: 12
        line 13: 19
        line 14: 27
        line 15: 43
        line 16: 60
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      61     0  args   [Ljava/lang/String;
            6      55     1     a   Ljava/lang/Integer;
           12      49     2     b   Ljava/lang/Integer;
           19      42     3     c   Ljava/lang/Integer;
           27      34     4     d   Ljava/lang/Integer;
      StackMapTable: number_of_entries = 4
        frame_type = 255 /* full_frame */
          offset_delta = 39
          locals = [ class "[Ljava/lang/String;", class java/lang/Integer, class java/lang/Integer, class java/lang/Integer, class java/lang/Integer ]
          stack = [ class java/io/PrintStream ]
        frame_type = 255 /* full_frame */
          offset_delta = 0
          locals = [ class "[Ljava/lang/String;", class java/lang/Integer, class java/lang/Integer, class java/lang/Integer, class java/lang/Integer ]
          stack = [ class java/io/PrintStream, int ]
        frame_type = 79 /* same_locals_1_stack_item */
          stack = [ class java/io/PrintStream ]
        frame_type = 255 /* full_frame */
          offset_delta = 0
          locals = [ class "[Ljava/lang/String;", class java/lang/Integer, class java/lang/Integer, class java/lang/Integer, class java/lang/Integer ]
          stack = [ class java/io/PrintStream, int ]
}

常量池(constant_pool)是在编译期被确定,并被保存在已编译的class文件中的一些数 据。除了包含代码中所定义的各种基本类型(如 int、long等)和对象型(如 String 及数组)的常量值外,还包含一些以文本形式出现的符号引用(Class中的常量池中数据会在加载的方式放进方法区中的运行时常量池中)。

Java中八种基本类型的包装类的大部分都实现了常量池技术,它们是Byte、Short、Integer、Long、Character、Boolean,另外两种浮点数类型的包装类(Float、Double)则没有实现。

何谓实现常量池技术?第一,基本类型(非包装类型)的值存放在常量池中;第二,包装类型(值在-128-127之间)的那些个对象以缓存数组的形式存放在常量池中。取的时候,拿到的是同一个。如Integer a=10;Integer b =10; 此时a==b为true!

另外Byte,Short,Integer,Long,Character这5种整型的包装类也只是在对应值在-128到127时才可使用常量池。如果常量值超过这个范围,就会从堆中创建对象,不再从常量池中取

Byte、Short、Integer、Long、Character都是使用类似缓存数组来实现常量池技术。Boolean只有两个值,直接声明了两个静态常量,值为对象。

ByteCache

  private static class ByteCache {
        private ByteCache(){}

        static final Byte cache[] = new Byte[-(-128) + 127 + 1];

        static {
            for(int i = 0; i < cache.length; i++)
                cache[i] = new Byte((byte)(i - 128));
        }
    }

Boolean的有点特殊:

 public static final Boolean TRUE = new Boolean(true);

    /**
     * The {@code Boolean} object corresponding to the primitive
     * value {@code false}.
     */
    public static final Boolean FALSE = new Boolean(false);

Float和Double每次都是new一个新对象出来:

 public static Float valueOf(float f) {
        return new Float(f);
    }

故而,如下两个Double不相等

Double d1=1.0;  

Double d2=1.0; 

因为Double 没有实现常量池技术,所以Doubled1=1.0;相当于Double d1=new Double(1.0)。d2类同,各自new了一个对象,所以d1和d2存放的指针不同,指向的对象不同,所以不相等。


是不是感觉理解了常量池技术?那么问题来了,缓存数组中的对象存放在什么地方?缓存数组又存放在哪里?

再回头看下代码:

 private static class ByteCache {
        private ByteCache(){}

        static final Byte cache[] = new Byte[-(-128) + 127 + 1];

        static {
            for(int i = 0; i < cache.length; i++)
                cache[i] = new Byte((byte)(i - 128));
        }
    }

缓存数组是个静态常量,理论上来说是存放在class文件的常量池,在类加载后会存放进方法区的运行时常量池中。


既然常量是放在常量池中,为什么说yte、Short、Integer、Long、Character、Boolean实现了常量池技术?

个人理解:它们各自维护了一个缓存数组对象放到常量池中,并不是把所有内部对象都扔到常量池,看起来像各自维护了一个对象池一样。

比如常量池中只有一个static final Byte cache[],而不是说常量池中有255个new Byte()需要常量池维护。

如有不同意见,欢迎留言交流!!

猜你喜欢

转载自blog.csdn.net/J080624/article/details/82317844