堆内存的结构(1.8以前)
看着图片来说一说我整理后理解的堆内存
新生代
新生代中分为三块区域,分别是Eden,Survivor From,Survivor To。
Eden用来存放每次新new出来的对象。
Survivor幸存区用来存放每次GC回收之后存活下来的对象。
GC后,通常会将Eden中存活下来的对象存在Survivor From中。假设现在我们已经有存活下来的对象存在于Survivor From当中,这个时候,Eden又产生了新的对象。那么,gc便会开始清空Eden和Survivor,并将存活下来的对象存到Survivor To当中。我们将Survivor To 和Survivor From的名称当做仅仅只是一个名称。现在,我们存有对象的幸存区只有Survivor To,然后这Survivor To和Survivor From的名称交换。使得在下一次有新对象产生时,从讲法上来说,仍然是将Eden和Survivor From区存活下来的对象存进Survivor To中。
说白了,就是 E+SF -> ST,然后E+ST->SF。循环清理。
老年代
在上面新生代中往复循环还能够一直存活下来的对象,就会进入到老年代中。
字符串的一些例子
1.6时,字符串常量池还是存在于永久代当中的,但1.7时,字符串常量池被移动到了堆内存当中。因此,来整理一下会导致的区别。
通过例子来讲
例1
String s1="Hello,"+"World";
String s2="Hello,World";
String s3=new String("Hello,World");
System.out.println(s1.equals(s2));
System.out.println(s1==s2);
System.out.println(s1==s3);
相信你一定知道答案了,没错答案就是
true
true
false
解析:s1的执行过程,会被JVM优化成,“Hello,World”,与s2等价。所以s1.equals(s2)是true,比较的是值
而s2在创建时,并不是在字符串常量池当中存一个字符串,而是找到s1字符串的引用返回。所以s1和s2其实是同一个引用。==比较的是地址,所以s1同样会等于s2。
引用s1的地址是字符串常量池中的,而引用s3存的地址是堆内存中“Hello,World”对象的,然后这个对象中存的是字符串常量池的引用地址。所以s1不等于s3
来看看本例的图示
例2
String s1="Hello,";
String s2="World";
String s3="Hello,World";
System.out.println(s3==(s1+s2));
System.out.println(s3=="Hello,"+"World");
相信你一定知道答案了,没错答案是
false
true
解析:s1+s2是两个不同的地址相加,自然不会跟s3的地址一样。而"Hello,"+“World”,字符串相加,是先相加,再去找是否存在相同的字符串,有的话则返回同一个引用,所以会和s3相等。
例3(并不是在实际编程的代码,考虑广一点)
String str=new String("Hello,World") 创建了几个对象?
相信你一定知道答案了,没错答案是
1个或2个
解析:这道题得分两个情况来讲。如果字符串常量池当中存在Hello,World,那么只会创建一个引用对象str,指向该对象。如果字符串常量池中不存在,那么会先创建一个对象“Hello,World”放到字符串常量池当中,再创建一个引用对象str指向该字符串。
例4(并不是在实际编程的代码,考虑广一点)
String str=new String("Hello")+new String("Hello");创建了几个对象?
相信你又知道答案了,没错答案是
3个或者4个
解析:如果字符串常量池中存在Hello,则是3个,如果不存在则是4个。分别是“Hello”,new String(“Hello”),new String(“Hello”),new String(“HelloHello”)。注意并不会去创建HelloHello字符串对象在常量池中
intern方法的作用和实例
作用
intern方法在1.6当中,如果字符串常量池中不存在该字符串,那么就会copy一份对象到字符串常量池当中。
intern方法在1.7当中,如果字符串常量池中不存在该字符串,那么就会copy一份对象的引用到字符串常量池当中。(所以返回的其实还是对象的地址)
例子(图示)
例5(来看看intern方法在jdk1.6和jdk1.7中的不同,实际编程)
String s1 = new String("Hello");
s1.intern();
String s2 ="Hello";
System.out.println(s1==s2);
相信你又知道答案了,没错答案是
jdk1.6中是false
jdk1.7中也是false
解析:本例中,String s1 = new String(“Hello”) 是创建了两个对象,一个存在于字符串常量池的“Hello”,一个存在于堆内存的 new String(“Hello”) 。
如图1.6:
如图1.7:这里的Hello是在创建时同时产生的,所以使用intern方法,并不会返回堆中对象的引用,而是让新建的S2直接指向了该字符串Hello
例6(来看看intern方法在jdk1.6和jdk1.7中的不同,实际编程)
String s3=new String("Hello")+new String("Hello");
s3.intern();
String s4="HelloHello";
System.out.println(s3==s4);
相信你又知道答案了,没错答案是
jdk1.6中是false
jdk1.7中是true(如果你去掉intern(),那么是false)
解析:来看看图示的方法
先来看看jdk1.6当中
在jdk1.6中,执行intern方法后,是复制了一份对象到方法区当中,所以S4直接指向到了方法区当中HelloHello的地址
再来看看jdk1.7当中
这里的原因就是,我们的字符串常量池当中并没有HelloHello。是在执行intern方法后,返回一份堆中对象的引用到字符串当中。所以我们的S4,在创建时,其实找到的是堆中对象的地址。