关于JDK1.7和JDK1.8的内存分配(String)

来源:互联网 发布:分析淘宝店铺数据软件 编辑:程序博客网 时间:2024/06/03 08:34

写在开头:这篇写完之后才发现好像没讲到jdk改版对内存的影响。。
这是一个很有趣的现象,如果没人告诉我的话遇到问题想必我会一头雾水。

首先来看一段代码:

String s1 = new String("A");System.out.println(s1); //AString s2 = s1.intern(); String s3 = "A";

将String作为一个引用类型,这对于先学Python的我来说是十分古怪的做法,因为Python中是没有字符只有字符串的,将字符串分出基本类型行列,诡异而无理:如此常用的类型竟不是基本类型?!

学过Java之后对类型、内存管理的概念更加清晰了不少,而这个例子让我十分清晰地认识了他们。

String s1 = new String("A"); //新建一个字符串对象A,并在常量池放入字符串ASystem.out.println(s1); //AString s2 = s1.intern(); //将s1对应的值从常量池中取出并赋值给s2String s3 = "A"; //s3在常量池中找到A,并将自己指向常量池中的ASystem.out.println(s2==s3); //trueSystem.out.println(s1==s3); //false

有趣的事情发生了,s1 s2 s3打印之后的结果都是 A ,但是s1与s2、s3并不相等。
画图理解下
我们创建一个变量之后,变量名便出现在了栈中
内存分布

String s1 = new String("A"); //新建一个字符串对象A,并在常量池放入字符串A

这时系统做的事是:在堆中创建了String对象,并在常量池中创建常量A
新建对象

String的描述是:常量。没错就是不可改变的量,那么在改变一个变量对应的字符串时,java其实是从新在常量池中新建了一个字符串,并指向新的常量(感觉这办法挺笨的)。

那么这时s1指向的是谁呢,自然是新建的对象,常量池中的A只不过是String在创建时发现常量池中没有,捎带创建的~
s1指向

String s2 = s1.intern(); //将s1对应的值从常量池中取出并赋值给s2String s3 = "A"; //s3在常量池中找到A,并将自己指向常量池中的A

知道了String常量池和对象分开之后,后两句就好理解多了。
s2其实是找s1问了他捎带创建的常量的地址(inter()方法返回对象的常量池位置),然后让自己指向了常量池中的A。
s3更直接了,直接说,常量池的A就是我家的!指向了常量池中的A。
s2s3
如此便很清晰了,s2 s3 和s1指向的地址都不一样,又怎么会相等呢哈哈。

接下来在看另一段代码

String s1 = new String("A")+new String("A");System.out.println(s1); //AAString s2 = s1.intern(); //此时s1=s2String s3 = "AA";System.out.println(s2==s3); //trueSystem.out.println(s1==s3); //true

第一次看到第一行的时候我就懵了,一个对象+一个对象,还有这种操作?
事实证明确实有,而且还能和其他对象一样活着(?),并没有什么毛病。
1
实际上则确实是在堆中创建了两个A对象,并在创建时捎带在常量池放入了一个A,第二个对象发现常量池里有A了之后便不会创建了。
再之后“+”施展了神奇的魔力!将两个对象捏(?)在了一起,创建了个新对象
AA!
然而因为是被+号连接起来的,并不是创建(new)的对象,所以人家并不会去瞄常量池,自然不会捎带创建常量AA了。

String s2 = s1.intern(); //此时s1=s2

2
这时我们的s2又去问s1地址了,“请问在你这工作的AA小姐姐家住哪里呀?”
“她没有家”s1冷漠地说道。
这可让s2犯了难,自己要去找AA的常量池地址,可人家偏偏没有。
没有怎么办?在居民区公告下AA住单位吧。
于是就在常量池留了个AA对象的地址,想找AA的都去AA对象里找。
java嫌麻烦,于是在编译阶段会让s2直接指向AA对象,这样s1和s2就相等了。

3
s3依然没问s1,我行我素去了居民区想直接找到AA小姐姐,却看到公告牌(地址)指向了AA对象。
没办法,去对象区找AA吧。
于是java在编译期又把s3指向了AA对象。。

System.out.println(s2==s3); //trueSystem.out.println(s1==s3); //true

这样就造成了开始和之前不一样的结果~

需要注意的是,常量池放地址的特性只有在jdk1.8才有,1.7及以前是没有的!
原因是如果创建超长字符串时如果在常量池和堆中各放一份就显得有些浪费了,于是干脆在常量池中放地址,节省空间~

还有,在jdk1.7时常量池才移入堆中,之前1.6的版本常量池是在方法区(永久带)中的,这个还不太熟,以后再填~

原创粉丝点击