String s = new String("xyz"); 创建了几个实例?

这是一道很经典的面试题,在一本所谓的Java宝典上,我看到的“标准答案”是这样的:

两个,一个堆区的“xyz”,一个栈区指向“xyz”的s。

这个所谓的“标准答案”槽点更多,后面我们会慢慢分析。

虽然答案很离谱,但是我觉得这个问题本身也不具有什么意义,因为问题没有既定义“创建”的具体含义,又没有指定“创建”的时间,是运行时吗?包不包括类加载的时候?有没有上下文代码语境?也没有定义实例是指什么实例,是指Java实例吗?还是单指String实例?包不包括JVM中的C++实例?

显然,这个问题是一个“有问题的问题”。这个答案也是一个“有问题的答案”。

String结构

在分析之前,为了能更好的理解后面的知识点,我们需要对Java中的String结构有一个大致了解:

从上图可以看出,String类有三个属性:

  • value:char数组,用于用于存储字符

  • hash:缓存字符串的哈希码,默认为0

  • serialVersionUID:序列化用的

正常的问题与“合理的解释”

在上面的题干上加上"String"限定词,可以得到一个比较合理的问题:

String s = new String("xyz");创建几个String实例?

对于这个问题,网上也有很多错误的答案和解析,我认为这个答案看起来比较合理:

两个,一个是字符串字面量"xyz"所对应的、存在于全局共享的常量池中的实例,另一个是通过new关键字创建并初始化的、内容(字符)与"xyz"相同的实例。如果常量池中如果已经存在这个字符串,就只会创建一个。同时在栈区还会有一个对new出来的String实例的引用s。

考虑到了栈与堆,提到了常量池,我认为这已经达到大部分面试官对这个题目答案的期许了,或许这也是面试官想要考察的点。

但这个答案也仅是比较合理,并不完全正确。

首先,我不理解的是为什么很多答主总是用“常量池”来代替“字符串常量池”,在Java体系中,其实是有三个常量池的,三个常量池的概念和用处都不相同,混淆在一起容易给别人造成误解。

其次,就算答主说的“常量池”就是“字符串常量池”,可“字符串常量池”中存的是String实例的引用,而不是字符串,这是有很大区别的。而且这个答案是没有考虑代码执行的环境。

这些问题,下面都会一一分析。

分清变量和实例

我们先回到开头的问题与“标准答案” :

String s = new String("xyz"); 创建了几个实例?

两个,一个堆区的“xyz”,一个栈区指向“xyz”的s

很明显写答案的人没有把变量和实例分清楚。在Java里,变量就是变量,类型的变量只是对某个对象实例或者null的,不是实例本身。声明变量的个数跟创建实例的个数没有必然关系。

举个例子:

String s1 = "xyz";
String s2 = s1.concat("");
String s3 = null;
new String(s1);

这段代码会涉及3个String类型的变量:

  • s1,指向下面String实例的1

  • s2,指向与s1相同

  • s3,值为null,不指向任何实例

以及3个String实例:

  • "xyz"字面量对应的驻留的字符串常量的String实例

  • ""空字符串字面量对应的驻留的字符串常量的String实例

  • 通过new String(String)创建的新String实例,没有任何变量指向它

类加载

对于String s = new String("xyz");创建几个String实例?这个问题。

似乎网上的所有答案都把类加载过程和实际执行过程合在一起分析的。看起来是没有什么问题的,因为想要执行某个代码片段,其所在的类必然要被加载,而且对于同一个类加载器,最多加载一次。

但是我们看一下这段代码的字节码:

字节码中似乎只出现了一次new java/lang/String,也就是只创建了一个String实例。也就是说原问题中的代码在每执行一次只会新创建一个String实例。

这里的ldc指令只是把先前在类加载过程中已经创建好的一个String实例("xyz")的一个引用压到操作数栈顶而已,并没有创建新的String实例。

不是应该有两个实例吗?还有一个String实例是在什么时候创建的呢?

还有一个String实例在类加载的时候创建。

我们都知道类加载的解析阶段是Java虚拟机将常量池内的符号引用替换为直接引用的过程,根据JVM规范,符合规范的JVM实现应该在类加载的过程中创建并驻留一个String实例作为常量来对应"xyz"字面量,具体是在类加载的解析阶段进行的。这个常量是全局共享的,只在先前尚未有内容相同的字符串驻留过的前提下才需要创建新的String实例。

所以你可以理解成:

在类加载的解析阶段,其实已经创建了一个String实例,执行代码的时候,又new了一个String实例。

JVM的优化

以上讨论都只是针对规范所定义的Java语言与Java虚拟机而言。概念上是如此,但实际的JVM实现可以做得更优化,原问题中的代码片段有可能在实际执行的时候一个String实例也不会完整创建(没有分配空间)。

不结合上下文代码来看就直接说是“标准答案”就是耍流氓。

我们看下这段代码:

运行这段代码,会不断的创建String对象吃内存,然后频繁的造成GC。对于这个结论相信大家都没有意见。

我们加上-XX:+PrintGC打印日志;加上-XX:-DoEscapeAnalysis关闭逃逸分析(JDK8默认开启此优化,我们先关闭)

运行一下看看:

结果确实如我们所料,不断的创建String对象吃内存,造成频繁GC。

我们现在将-XX:-DoEscapeAnalysis改成-XX:+DoEscapeAnalysis,重新跑一下这段代码:

神奇的事情发生了,继续跑下去也没有再打出GC日志了。难道新创建String对象都不吃内存了么?

实际情况是:经过HotSpot VM的的优化后,newString()方法不会新创建String实例了。这样自然不吃内存,也就不再触发GC了。

现在再来看开篇的那个问题,不结合具体情况,还能简单的说String s = new String("xyz");会创建两个String实例吗?

我只是举了一个逃逸分析的例子,HotSpot VM还有很多像这样的优化,比如方法内联、标量替换和无用代码削除。这些都会影响对象的创建的。

klass-oop

如果题干上没有加上“Java”实例的定语,那JVM中的oop实例我们也不应该忽略。

为了后面能更好的说清楚这一点,先大致的介绍一下klass-opp模型。先做一个约定,全文只要涉及JVM具体实现的内容都是基于Jdk8中HotSpot VM展开的。

HotSpot VM是基于C++实现,而C++是一门面向对象的语言,本身是具备面向对象基本特征的,所以Java中的对象表示,最简单的做法是为每个Java类生成一个C++类与之对应。但HotSpot VM并没有这么做,而是设计了一套klass-oop模型。

klass,它是Java类的元信息在JVM中的存在形式。一个Java类被JVM类加载器加载之后,就是以klass的形式存在于JVM之中。

oop,它是Java对象在JVM中的存在形式。每创建一个新的对象,在JVM内部就会相应地创建一个对应类型的OOP对象。

其中instanceOopDesc表示非数组对象;

arrayOopDesc表示数组对象;

而objArrayOopDesc表示引用类型数组对象;

typeArrayOopDesc表示基本类型数组对象。

举个例子:Java中String类的一个实例,在JVM中会有一个对应的instanceOopDesc实例。

字符串常量池

在Java体系中,有三种常量池:

  • class字节码中的常量池:存在于硬盘上。主要存放字面量和符号引用。

  • 运行时常量池:方法区的一部分。我们常说的常量池,就是指这一块区域。

  • 字符串常量池:存在于堆区。这个常量池在JVM层面就是一个StringTable,只存储对java.lang.String实例的引用,而不存储String对象的内容。一般我们说一个字符串进入了字符串常量池其实是说在这个StringTable中保存了对它的引用,反之,如果说没有在其中就是说StringTable中没有对它的引用。

今天,我们要了解的是字符串常量池。

字符串常量池,即String Pool。在JVM中对应的类是StringTable,底层实现是一个Hashtable。利用的是哈希思想。

看一段往字符串常量池添加字符串引用的方法:

上面面这段代码虽然是C++写的,但我相信学过Java的人都能看懂,至少也能明白这段代码干了什么事情。无非是通过String的内容+长度生成的hashValue值定位下标index,然后将Java的String类的实例对应的instanceOopDesc封装成HashtableEntry作为存储结构存储到常量池。

补充完字符串常量池的知识之后,我们再回到文章开头的那一题:

String s = new String("xyz");创建了几个实例?

如果包括JVM中的C++实例的话,有两个Java的String实例,两个String实例对应的instanceOopDesc实例,还有一个char[]数组对应的typeArrayOopDesc实例,加一起一共是5个。也可以说2个String实例加上3个oop实例。

不理解的可以看下面这张内存图(图中省略了两个String对应的instanceOopDesc实例)。

总结

String s = new String("xyz"); 创建了几个实例?

通过以上的分析,我们会发现,每在这道题目的题干上每加一个定语,这道题目就会有不同的答案。是否考虑类加载过程,是否考虑JVM优化,是否包括对应的oop实例等等等等,都会有不同的答案。

如果下次有人问你这个问题,不妨把这篇的文章分享给他。

写在最后

为了写这一篇文章,我翻看了很多@RednaxelaFX前辈和周志明前辈的博客,过程中收益良多。在这里感谢前辈们为国内JVM的科普与发展做出的贡献!

文中涉及代码:https://github.com/xiaoyingzhi/blog

参考文章:

http://isfeasible.cn/posts/view/5b84b6ab3957bb300a5bca94

https://www.iteye.com/blog/rednaxelafx-774673

http://lovestblog.cn/blog/2014/06/28/hsdb-string/

有道无术,术可成;有术无道,止于术

欢迎大家关注Java之道公众号

好文章,我在看❤️

String s = new String(xyz)创建了几个实例你真的能答对吗?相关推荐

  1. 工作10年后,再看String s = new String(xyz) 创建了几个对象?

    点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 转自:艾小仙 这个问题相信每个学习java的同学都不陌生, ...

  2. 请别再拿“String s = new String(xyz);创建了多少个String实例”来面试了吧---转

    http://www.iteye.com/topic/774673 羞愧呀,不知道多少人干过,我也干过,面壁去! 这帖是用来回复高级语言虚拟机圈子里的一个问题,一道Java笔试题的.  本来因为见得太 ...

  3. String s = new String(“xyz“);创建了几个字符串对象?

    题目:String s = new String("xyz");创建了几个字符串对象?() [单选题] 答案:两个或者一个都有可能 String str = "name& ...

  4. Java面试宝典_君哥讲解笔记04_java基础面试题——String s=new String(“xyz“);创建了几个String Object、equals和hashCode、hashCode(

    java基础面试题目录 文章目录 java基础面试题目录 前言 String s=new String("xyz");创建了几个String Object[重要] 全面理解: St ...

  5. string s = new string(“xyz“);创建了几个对象_「005」-JavaSE面试题(五):String类

    第一期:Java面试 - 100题,梳理各大网站优秀面试题.大家可以跟着我一起来刷刷Java理论知识 [005] - JavaSE面试题(五):String类 第1问:String.StringBuf ...

  6. Java:面试题:String s=new String(abc)创建了几个对象?

    String str=new String("abc");   紧接着这段代码之后的往往是这个问题,那就是这行代码究竟创建了几个String对象呢? 相信大家对这道题并不陌生,答案 ...

  7. 美团面试题:String s = new String(111)会创建几个对象?

    点击关注公众号,Java干货及时送达 来源:blog.csdn.net/o9109003234/article/details/109523691 String不可变吗? public class A ...

  8. java中字符串的创建_【转载】 Java中String类型的两种创建方式

    本文转载自 https://www.cnblogs.com/fguozhu/articles/2661055.html Java中String是一个特殊的包装类数据有两种创建形式: String s ...

  9. 云端飘 String s=new String(abc)创建了几个对象?

    转自:http://www.cnblogs.com/ydpvictor/archive/2012/09/09/2677260.html -------------------------------- ...

最新文章

  1. 获取Android studio 中的模拟器的界面的点的坐标(Ubuntu)
  2. dabs是什么意思_单词flounder是什么中文意思
  3. 20170817 - 今日技能封装 - A
  4. 配置Goldengate支持DDL
  5. sql 无法删除当前数据库,因为当前数据库正在使用
  6. Android开发之跨进程通信-广播跨进程实现方法(附源码)
  7. stm32l0的停止模式怎么唤醒_Mac外接显示器的显示模式怎么设置
  8. 图解Android 内存分析工具之Mat使用教程
  9. large margin-人脸识别
  10. MX160煲机音乐的选择
  11. shuffleNet实现
  12. python c语言实现_使用C语言为python编写动态模块(3)--在C中实现python中的类
  13. opencv去除图片黑边,黑色背景
  14. 异常检测方法——DBSCAN、孤立森林、OneClassSVM、LOF、同比环比、正态分布、箱线图
  15. Kingdom Rush 国王保卫战图文攻略
  16. Java学习成长路径
  17. 全“芯”升级,浩辰CAD 2021赋能全国产化CAD应用
  18. Process finished with exit code 134
  19. 斩获 offer 的 Java 面试宝典
  20. OpenCV学习17_ 分水岭算法

热门文章

  1. linux arp代理配置,linux下tomcat的arp配置
  2. linux6.5怎样安装vim,在Centos 6.5下成功安装和配置了vim7.4
  3. android 华为手机灭屏搜索不到蓝牙_华为Mate 30更新EMUI10.1.0.132版本,新增10项实用功能...
  4. linux 脚本 if判断 o,shell脚本常用脚本:if判断
  5. matlab图像降噪_图像超分:RealSR
  6. 动态规划 - 九度OJ 1480
  7. SQL注入:4、数据库可写
  8. thrift oneway的问题
  9. linux ipconfig和route 命令
  10. 数据相关,资源相关,控制相关的解决方法