结合JVM深入理解Java字符串

既然题目里就提到了JVM,那么首先必然要奉上两张图。


来自阿里《码出高效:Java开发手册》

来自《深入理解Java虚拟机(第二版)》

HotSpot JVM内存模型已经是老生常谈的知识了,所以这里也就不再赘述。直接说String。
在String类的JavaDoc开头,就有这样一句话:

Strings are constant; their values cannot be changed after they are created. String buffers support mutable strings. Because String objects are immutable they can be shared.

也就是说,String是不可变类。与它类似,基本类型的包装类也都是不可变类。字符串是常量,一旦初始化之后就不能更改。如果需要可变的字符串,就要借助StringBuilder和StringBuffer了。
为什么说String是不可变的?因为在它的内部是用一个final char数组来存储的。

private final char value[];
仍然举一个例子
        String s1 = "LittleMagic";
        String s2 = "LittleMagic";
        String s3 = new String("LittleMagic");
        String s4 = s3.intern();
        String s5 = "Little" + "Magic";
        String s6 = "LittleMagic2";
        String s7 = s2 + 2;
        System.out.println(s1 == s2); // true
        System.out.println(s2 == s3); // false
        System.out.println(s2 == s4); // true
        System.out.println(s2 == s5); // true
        System.out.println(s6 == s7); // false

这段代码的字节码如下。

       0: ldc           #2                  // String LittleMagic
       2: astore_1
       3: ldc           #2                  // String LittleMagic
       5: astore_2
       6: new           #3                  // class java/lang/String
       9: dup
      10: ldc           #2                  // String LittleMagic
      12: invokespecial #4                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
      15: astore_3
      16: aload_3
      17: invokevirtual #5                  // Method java/lang/String.intern:()Ljava/lang/String;
      20: astore        4
      22: ldc           #2                  // String LittleMagic
      24: astore        5
      26: ldc           #6                  // String LittleMagic2
      28: astore        6
      30: new           #7                  // class java/lang/StringBuilder
      33: dup
      34: invokespecial #8                  // Method java/lang/StringBuilder."<init>":()V
      37: aload_2
      38: invokevirtual #9                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      41: iconst_2
      42: invokevirtual #10                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
      45: invokevirtual #11                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      48: astore        7
      50: getstatic     #12                 // Field java/lang/System.out:Ljava/io/PrintStream;
      53: aload_1
      54: aload_2
.................

下面来逐个分析。

  • s1 == s2
    类似"LittleMagic"这样的字符串,我们叫它字符串字面量(String literal),后面简称字面量。
    字面量对象是存储在字符串常量池中的。采用字面量的方式创建字符串,JVM首先会在字符串常量池中寻找字面量为“LittleMagic”的字符串对象,如果不存在,就创建这个对象,并且返回它的引用;如果存在的话,就会直接返回它的引用。所以s1与s2的地址是相同的。
  • s2 != s3
    s3是new出来的字符串对象,它会在堆内存中分配一个新的地址。它的地址与字符串常量池中s2的引用地址自然是不同的。
    还有一个问题就是,s3这条语句一共创建了几个对象?答案是2个,即堆上的对象,以及JVM栈中对它的reference。但是,如果前面没有创建过相同的字面量的话,那么还得加上字面量本身,也就是3个。
  • s2 == s4
    这里就涉及到String.intern()方法的含义。在JDK1.8源码中,该方法的注释如下:

A pool of strings, initially empty, is maintained privately by the
class {@code String}.
When the intern method is invoked, if the pool already contains a string equal to this {@code String} object as determined by the {@link #equals(Object)} method, then the string from the pool is returned. Otherwise, this {@code String} object is added to the pool and a reference to this {@code String} object is returned.

意思就是,如果字符串常量池中已经存在一个字面量上相等(用String.equals()方法判定)的字符串,就返回常量池中的字符串。否则,就将这个字符串加入常量池,并返回它的引用。这样理解,s2与s4相等就是自然的了。

  • s2 == s5
    从上面的字节码中可以看出,s5由两个字面量相连接,在字节码中体现出来的是连接后的结果,即“LittleMagic”。也就是说,字面量做“+”运算能够在编译期就确定值,最终还是归于字符串常量池中对象的比较。
  • s6 != s7
    仍然从字节码中可以看出,s7 = s2 + 2这条语句,实际上是new出了一个StringBuilder,然后调用其append()方法来做连接。亦即与上面的情况相反,如果“+”运算中存在字符串引用的话,就会创建新的对象了,因为引用对应的值在编译期是无法确定的。
    由此也可以得知,不要在循环中使用类似s7 = s2 + 2这种调用方法,因为每次循环都要创建StringBuilder对象,拖累运行效率。
TBD
  • 字符串常量池随JDK版本的变化,位置有哪些变迁?是如何实现的?
  • 字符串常量池里存放的到底是什么?对象,引用,还是兼而有之?
  • 其他两种JVM管理的常量池(运行时常量池,class常量池)又是怎么回事?
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,826评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,968评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,234评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,562评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,611评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,482评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,271评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,166评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,608评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,814评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,926评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,644评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,249评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,866评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,991评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,063评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,871评论 2 354

推荐阅读更多精彩内容

  • 从网上复制的,看别人的比较全面,自己搬过来,方便以后查找。原链接:https://www.cnblogs.com/...
    lxtyp阅读 1,346评论 0 9
  • 注:都是在百度搜索整理的答案,如有侵权和错误,希告知更改。 一、哪些情况下的对象会被垃圾回收机制处理掉  当对象对...
    Jenchar阅读 3,224评论 3 2
  • 前言 RTFSC (Read the fucking source code )才是生活中最重要的。我们天天就是要...
    二毛_coder阅读 455评论 1 1
  •   需要说明的一点是,这篇文章是以《深入理解Java虚拟机》第二版这本书为基础的,这里假设大家已经了解了JVM的运...
    Geeks_Liu阅读 14,016评论 5 44
  • String类 先看一下源码(jdk1.8.0_144)中的对于类的定义 首先可以看到String类是被final...
    efan阅读 593评论 1 1