Java基础之String漫谈(三)

@[toc]

1. 导读

    这期主要分享下String的常量池以及intern方法的使用;

2. JVM内存模型的简介

在介绍String的常量池之前, 先插播下JVM的内存模型, 以便能更好地理解后面的内容;


这是一张经典的JVM运行时内存管理图(基于JAVA SE 8), 而我们经常用的HotSpot虚拟机则使用永久代(Permanent Generation)来实现方法区;
本期需要关注的是方法区中运行时常量池, JAVA堆和Java虚拟机栈;
.1 运行时常量池: 他存储两部分数据: 第一部分是class文件中描述的符号引用以及编译产生的常量和直接引用数据; 第二部分是运行时产生的新的常量也会存储在这里, String::intern就是运用了这一特性去拿String常量池的数据的;

.2 Java堆: 简单来说我们每个new出来的对象都会存储在这个区域;

.3 Java虚拟机栈: 这部分存储是方法运行时的数据, 而存储的元素叫做栈帧;
这里先形成个概念, 不同版本的JVM的内存模型也是不同的, 这里只是拿这个举例, 具体等到JVM专题再展开;

3. String::intern

    public native String intern();

.1 String::intern是String类中唯一的一个原生方法;

.2 他的作用就是在运行时, 调用String::intern构建String对象时, 先从常量池获取, 如果不存在则新增, 并将新增的String对象放入常量池;

.3 请注意, 我上面说明的是常量池中存的String对象而不是引用; JDK8的源码中有段注释:

    /**
     * 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::itern被调用时, 会先去StringPool中找是否存在当前的字符串(使用equals判断), 如果不存在会先把String对象添加到StringPool中, 然后返回这个对象的引用;
下面做个实验来说明上面的理论:

    public static void main(String[] args) {
        
        String str1 = "123";
        String str2 = new String("123");
        /**
         * 不主动调用intern的情况下 两者虽然底层value[]一致, 但是引用的是两个String对象;
         */
        LOGGER.info(String.format("不调用String::intern的地址引用: %s", str1 == str2));
        LOGGER.info(String.format("不调用String::intern的对象值是否相同: %s", str1.equals(str2)));
        /**
         * 主动调用intern
         */
        String str3 = new String("123").intern();
        LOGGER.info(String.format("调用String::intern的地址引用: %s", str1 == str3));
        LOGGER.info(String.format("调用String::intern的对象值: %s", str1.equals(str3)));
        
        char[] tempArray = {'1', '2', '3'};
        String str4 = new String(tempArray).intern();
        LOGGER.info(String.format("调用String::intern的地址引用: %s", str1 == str4));
        LOGGER.info(String.format("调用String::intern的对象值: %s", str1.equals(str4)));
        
    }

.1 str1是直接声明的字符串常量, 会在初始化就把"123"放入到常量池中;
.2 str2是使用"123"再new了一个新的String对象;
.3 str3是new String对象时, 调用了String::intern;
.4 str4是基于char[]new了一个String对象, 并且调用String::intern;
回到上面的结论, 常量池中是存的对象, 且只有调用String::intern时, 才会去常量池中寻找匹配的String对象;那么上面的结果应该是:
. case 1: false(未调用String::intern, 直接返回了新对象(存储在java堆中));
. case 2: true(底层的value[]里面的值相同);
. case 3: true(调用了String::intern, 且在常量池已存在, 直接返回该对象的引用<与str1的对象相同>);
. case 4: true;
. case 5: true(调用了String::intern, 从常量池获取);
. case 6: true;

实际执行的结果也是与我们分析的一致;

划重点:
.1 String::intern是对字符串的缓存, 缓存位置是常量池;
.2 String::intern判断字符串是否相同使用的是String::equals, 故而不管用何种方式声明的字符串, 只要最终的value[]是相同的, 那么都会从常量池获取;
.3 JVM对String str = "1" + "2" + "3"; 这种方式的声明会自动优化成String str = "123"; 所以常量池中只会缓存一个String对象;

4. String::intern的利弊

String::intern的初衷是运用缓存的方式, 提升效率; 但是这种方式真的好吗?
在JDK6的时代, String的常量池还是位于方法区的(永久代), 这块内存区域是有界的, 并且GC效率不高; 假设我们调用String::intern时, 次数高但是相同的String较少, 这时候会导致不断地往方法区中添加String对象, 达到临界值时, 自然就内存溢出(OOM)了;
故而在JDK6时代, 是不推荐使用String::intern的;

在JDK7之后的版本, String的常量池被移动到了堆中, 虽然堆也是有界的, 但是他的大小要比方法区大, 并且堆的GC更频繁, OOM的概率就小了; 但是如果是上面的场景, 依然有概率会导致OOM的;

所以我们在使用String::intern时, 需要考虑实际场景, 如果有大面积的重复字段时, 可以使用String::intern将其缓存起来, 比如用到省份或者省会这种场景; 但是如果是无序并且存在时间较短的字符串, 就没必要将其缓存起来了;

划重点:
.1 JDK6的常量池是位于方法区的, 到了JDK7之后才将其移到java堆的; 故而使用JDK6时, 不推荐使用String::intern, 如果实在需要, 可以考虑自己实现一个HashMap将重复的字符串缓存起来;
.2 使用String::intern时需要考虑实际场景, 如果是无序且存在时间较短的场景, 就没有必要使用String::intern来增加CPU开支了; 如果有涉密以及安全考虑, 也不推荐使用String::intern;
.3 在大多数情况下, 我们该怎么使用String就怎么使用, 不必强求String::intern;

关于String的分享就到这里了, 以上内容如有不足之处, 欢迎指正; 如果看了觉得有收获, 请转发并收藏, 感谢;

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容

  • 首先,来一道String的考题,代码如下: 先不着急给出答案,我们来一步一步得进行分析,争取覆盖String大部分...
    HRocky阅读 291评论 0 0
  • String 的声明 由 JDK 中关于String的声明可以知道: 不同字符串可能共享同一个底层char数组,例...
    CodeKing2017阅读 1,607评论 1 2
  •   需要说明的一点是,这篇文章是以《深入理解Java虚拟机》第二版这本书为基础的,这里假设大家已经了解了JVM的运...
    Geeks_Liu阅读 13,964评论 5 44
  • 本文是我自己在秋招复习时的读书笔记,整理的知识点,也是为了防止忘记,尊重劳动成果,转载注明出处哦!如果你也喜欢,那...
    波波波先森阅读 820评论 1 6
  • “开卷有益”,说的是多读书是有好处的,
    梧桐lxr阅读 94评论 0 0