首先,我们简单了解一下运行时数据区,这里是java虚拟机运行程序时的内存区域。有方法区,堆,虚拟机栈,本地方法栈,程序计数器。方法区和堆是线程共享的,其他三个是线程私有的。然后我们看线程共享的方法区,里面存储着类信息,常量,静态变量,即时编译器编译后的代码等数据。然后,运行时常量池,就在堆里面(jdk1.7前在方法区里面)。在class文件中就存有常量池的信息,运行时就把class文件里面的常理池搞到堆中。

然后,我们来看看这个常量池的作用,它的存在意义是什么?

它主要是为了快速的创建某些对象而产生的,同时避免频繁的创建和销毁对象而影响系统性能,其实现了对象的共享,比如说字符串“abc”,你要生成同一个字符串,你总不能一直在内存中给相同字符串创建不同的内存空间吧,这样的话就很浪费内存了。所以在常量池中存放的东西,只要是相同的,那么就不会产生新的对象,而是用那一个对象。

来,看代码吧,看代码理解得快。

Integer i1 = 100;

Integer i2 = 100;

System.out.println(i1 == i2);

这段代码最后的返回是true。我们先有基础的理解先,这里的i1和i2都是对象。然后 “==”号在比较对象时是比较对象在内存中的地址是否一致。然后,通过结果我们发现这两个对象其实是指向同一个内存地址。这就表示他们是在常量池中那一个对象。然后,我们再来看看其他代码:

Integer i1 = new Integer(1);
Integer i2 = new Integer(1);
System.out.println(i1 == i2);
Integer i3 = 200;
Integer i4 = 200;
System.out.println(i3 == i4);

这上面两个都是返回false,这里来解释一下:第一个是因为它使用了new关键字,所以产生的对象是在堆内存中的,也是线程共享区域。然后由于是两个不同的对象,所以在堆内存中的额内存地址不同,所以返回false。

第二个是因为这个整型数在常量池中的范围是-128-127,当不在这个范围内的整数在用上面这种方式创建时都是存储在堆内存中的,所以地址也不同,所以返回false。关于这点,可以看源码:

public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}

为什么要看这个源码呢?因为你integer i1=100;时它就是调用integer类的valueof静态方法,即相当于integer.valueof(100);可参考这篇文章 Java Integer 常量池 这里推荐一个反编译工具 java-decompiler/jd-gui 下载带windows的zip即可,然后打开相应的class文件就可以具体看出它调用什么方法。

low为-128,high为127在interger里面可以链接找到。

为什么要取这个范围内呢?因为这个范围内的数明显比其它范围的数更加常用,如果不重复创建,占内存的话,这样就有助于提高时空间效率。

而对于double和float之类的,很明显我们不知道该存什么在常量池中好,因为它有不确定的小数点,不像integer那样在一个范围内是确定的数值。其它的基本数据类型和封装类都有常理池的应用。

(其实我一直很好奇这个常量池中的东西是提前存储好的吗?(比如-128到127这些数)然后等我们有需要时再给个指向?(可是如果这样的话那字符串常量池不就显得很大,很占内存了吗?)还是说等我们有需要时,再在常量池中创建,然后再取出来?)

没错,经过多方思考,加上源码的参考,得出结论对于integer类,他是直接在从原来存储好的-128到127中取出来的。由上面的源码可知。 关于IntegerCache这个类,注释是The cache is initialized on first usage。也就是提前帮你搞好了,不管你用不用到都放在那了。然后看字符串常量池,它则是在编译的时候看你这里有什么,比如说string s1="abc",然后才放进字符串常量池里,如果后面再有string s2="abc",那么就不再创建,直接引用前面的了。

然后我们再来看字符串的常量池。

String s1=new String("abc");
String s2=new String("abc");
System.out.println(s1==s2) //返回false
String s3="abc";
String s4="abc";
System.out.println(s3==s4);//返回true

第一个是堆内存中的不同对象所以false。第二个是在常量池中,所以true.

String s1 = "123";
String s2 = "456";
String s3 = s1 + s2;
String s4 = "123456";
String s5 = new String("123456");

System.out.println(s3 == s4);//s3在堆中,s4在常量池中 falseSystem.out.println(s3 == s5);//在堆中的两个不同对象 false

上面是反应另一种情况的。返回都是false。s1和s2都是在常量池中,但是由于运算符 “+”号的影响,jvm会在从常量池中把s1和s2复制到堆中,即在堆内存中创建s1和s2对象,然后相加,在堆中再创建一个s3对象。所以此时在堆中有三个对象实例。后面的看看注释就懂了。

然后看字符串的一个特殊方法 intern(),它的作用就是在常量池中创建字符串对象。

String s4 = "123456";
String s5 = new String("123456");
String s6 = s5.intern();
System.out.println(s4 == s6);

最后输出是true。说明s4和s6是同一个对象,本来intern是要在常量池中创建一个字符串对象的,然后发现已经有了,所以直接指向s4这个字符串对象了。

然后抄一句《深入理解java虚拟机》里面的一段:

虚拟机遇到一条new指令时,首先去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载,解析和初始化过,如果没有,就执行相应的初始化。。把一块确定大小的内存中java堆中划分出来。

然后看一段代码:

string s1= new string("abc") 问:这里产生了几个对象?

根据上面的意思,我们可以这样说,如果常量池中已经有了abc对象,那么久只会在堆中产生一个对象。如果没有,那么先在常量池中创建一个对象,然后复制到堆内存中再创建一个。所以创建了两个。java常量池www.jianshu.com

java常量池 final java常量池在方法区还是堆_bc

忆蓉之心:这就是String常量池啊?zhuanlan.zhihu.com

java常量池 final java常量池在方法区还是堆_java常量池_02

最近脑子有点懵,感觉不对的地方欢迎讨论。