A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© 君嘘 中级黑马   /  2015-4-17 21:41  /  947 人查看  /  12 人回复  /   0 人收藏 转载请遵从CC协议 禁止商业使用本文

转载:http://my.oschina.net/xiaohui249/blog/170013
我和此博主的简介一致,所以就转载过来了,字数限制只显示部分,大家可以点开连接去看看。
我也只是想知道String类型的内存机制到底是怎么样的,网上众说纷纭,而且思路模糊。
这博主的思路还是比较清晰的。
             在讨论String的定义方法之前,先了解一下常量池的概念,前面在介绍方法区的时候已经提到过了。下面稍微正式的给一个定义吧。
       常量池(constant pool)指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。它包括了关于类、方法、接口等中的常量,也包括字符串常量。常量池还具备动态性,运行期间可以将新的常量放入池中,String类的intern()方法是这一特性的典型应用。不懂吗?后面会介绍intern方法的。虚拟机为每个被装载的类型维护一个常量池,池中为该类型所用常量的一个有序集合,包括直接常量(string、integer和float常量)和对其他类型、字段和方法的符号引用(与对象引用的区别?读者可以自己去了解)。
       String的定义方法归纳起来总共为三种方式:
  •        使用关键字new,如:String s1 = new String("myString");
  •        直接定义,如:String s1 = "myString";
  •        串联生成,如:String s1 = "my" + "String";这种方式比较复杂,这里就不赘述了

       第一种方式通过关键字new定义过程:在程序编译期,编译程序先去字符串常量池检查,是否存在“myString,如果不存在,则在常量池中开辟一个内存空间存放“myString”;如果存在的话,则不用重新开辟空间,保证常量池中只有一个“myString”常量,节省内存空间。然后在内存堆中开辟一块空间存放new出来的String实例,在栈中开辟一块空间,命名为“s1”,存放的值为堆中String实例的内存地址,这个过程就是将引用s1指向new出来的String实例。各位,最模糊的地方到了!堆中new出来的实例和常量池中的“myString”是什么关系呢?等我们分析完了第二种定义方式之后再回头分析这个问题。
       第二种方式直接定义过程:在程序编译期,编译程序先去字符串常量池检查,是否存在“myString”,如果不存在,则在常量池中开辟一个内存空间存放“myString”;如果存在的话,则不用重新开辟空间。然后在栈中开辟一块空间,命名为“s1”,存放的值为常量池中“myString”的内存地址。常量池中的字符串常量与堆中的String对象有什么区别呢?为什么直接定义的字符串同样可以调用String对象的各种方法呢?
       带着诸多疑问,我和大家一起探讨一下堆中String对象和常量池中String常量的关系,请大家记住,仅仅是探讨,因为本人对这块也比较模糊。
       第一种猜想:因为直接定义的字符串也可以调用String对象的各种方法,那么可以认为其实在常量池中创建的也是一个String实例(对象)String s1 = new String("myString");先在编译期的时候在常量池创建了一个String实例,然后clone了一个String实例存储在堆中,引用s1指向堆中的这个实例。此时,池中的实例没有被引用。当接着执行String s1 = "myString";时,因为池中已经存在“myString”的实例对象,则s1直接指向池中的实例对象;否则,在池中先创建一个实例对象,s1再指向它。如下图所示:

       这种猜想认为:常量池中的字符串常量实质上是一个String实例,与堆中的String实例是克隆关系。

       第二种猜想也是目前网上阐述的最多的,但是思路都不清晰,有些问题解释不通。下面引用《JAVA String对象和字符串常量的关系解析》一段内容。
      
在解析阶段,虚拟机发现字符串常量"myString",它会在一个内部字符串常量列表中查找,如果没有找到,那么会在堆里面创建一个包含字符序列[myString]的String对象s1,然后把这个字符序列和对应的String对象作为名值对( [myString], s1 )保存到内部字符串常量列表中。如下图所示:


            如果虚拟机后面又发现了一个相同的字符串常量myString,它会在这个内部字符串常量列表内找到相同的字符序列,然后返回对应的String对象的引用。维护这个内部列表的关键是任何特定的字符序列在这个列表上只出现一次。


           虽然这段内容不那么有说服力,但是文章提到了一个东西——字符串常量列表,它可能是解释这个问题的关键。
           文中提到的三个问题,本文仅仅给出了猜想,请知道真正内幕的高手帮忙分析分析,谢谢!

  •            堆中new出来的实例和常量池中的“myString”是什么关系呢?
  •            常量池中的字符串常量与堆中的String对象有什么区别呢?
  •            为什么直接定义的字符串同样可以调用String对象的各种方法呢?      

12 个回复

倒序浏览
了解了内存中是怎么存储和转换,就更好了解String类了,特别是常量池,String 是个特殊而且非常常用的类!
回复 使用道具 举报
caotierong 发表于 2015-4-17 21:45
了解了内存中是怎么存储和转换,就更好了解String类了,特别是常量池,String 是个特殊而且非常常用的类! ...

就是因为不了解,网上说法太多了。我也问了黑马的老师,他又给我发了个网址叫我去看,又是另外一种说法。
回复 使用道具 举报
君嘘 发表于 2015-4-17 21:56
就是因为不了解,网上说法太多了。我也问了黑马的老师,他又给我发了个网址叫我去看,又是另外一种说法。 ...

你看的是什么视频,建议去看刘意的视频,里面讲解的很清晰。
回复 使用道具 举报
建议看毕向东老师的视屏,讲的详细,易理解。
回复 使用道具 举报
大神图解
回复 使用道具 举报
堆中new出来的实例和常量池中的“myString”是什么关系呢?
    没关系。两者是内容相同(在String类重写的equals方法的意义下)但地址不同的两个对象
常量池中的字符串常量与堆中的String对象有什么区别呢?
    同上一题
为什么直接定义的字符串同样可以调用String对象的各种方法呢?
    只要确定这个字符串是String类型,就可以调用String对象的方法。无非是做个标记或者编译器针对字符串字面值特殊处理一下的问题
回复 使用道具 举报
还是视频容易理解啊
回复 使用道具 举报
君嘘 中级黑马 2015-4-17 22:48:18
9#
fantacyleo 发表于 2015-4-17 22:23
堆中new出来的实例和常量池中的“myString”是什么关系呢?
    没关系。两者是内容相同(在String类重写的 ...

我的疑问:
1.常量池中存储的是对象还是字面值。
    如果是字面值,那么为什么说String s=new String("abc")是新建了两个对象。

如果问题1是存储的是字面值,那么问题2
2.String s=“abc”那么在堆内存中是否存在“abc”。
   同上,如果堆内存中不存在,为什么说String s=new String("abc")是新建了两个对象
回复 使用道具 举报
6666666666666
回复 使用道具 举报
君嘘 发表于 2015-4-17 22:48
我的疑问:
1.常量池中存储的是对象还是字面值。
    如果是字面值,那么为什么说String s=new String("a ...

1. 当然是对象。怎么可能只存字面值?
2. 并不是在堆内存中存储的才是对象,只是说new创建的对象都在堆内存中存储
回复 使用道具 举报
本帖最后由 君嘘 于 2015-4-17 23:53 编辑
fantacyleo 发表于 2015-4-17 23:06
1. 当然是对象。怎么可能只存字面值?
2. 并不是在堆内存中存储的才是对象,只是说new创建的对象都在堆内 ...

好的 谢谢 我有点懂了。

回复 使用道具 举报
能理解字符串在常量池一旦初始化就不可改变,但其引用指向可变的话,下面就好理解好多的说..常量池在方法区……
直接赋值的话直接指向常量池的地址值初始化了,new的话是在堆内存开辟了String类型(构参为String 类型)的空间,String该类本身继承了根类Object并改写了toString()方法,它返回值就是他本身。
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马