JVM虚拟机之堆内存的结构

本文深入解析Java 1.8前的堆内存结构,包括新生代与老年代的分区,以及字符串常量池的变化。通过具体示例,探讨字符串创建、连接与intern方法在不同JDK版本中的表现。

堆内存的结构(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,在创建时,其实找到的是堆中对象的地址。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值