欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

从JVM看String及intern方法

程序员文章站 2022-07-10 21:22:49
前言学习JVM过程中,遇到String的intern()方法,然后在网上找了很多发现都不怎么系统,很多说法也都不一致。所以笔者决定深入研究一下,以此记录下来。先看一下intern()的意义:简单说就是放入常量池具体实现方式:Jdk1.6及之前......

前言

学习JVM过程中,遇到String的intern()方法,然后在网上找了很多发现都不怎么系统,很多说法也都不一致。所以笔者决定研究一下,以此记录下来。

先看一下intern()的意义

简单说就是放入字符串常量池

实现方式的演变

从JVM看String及intern方法

  • JDK1.7之前:调用这个方法,先去字符串常量池中看是否已经存在,如果已经存在,那么直接返回这个常量在字符串常量池中的地址值,如果不存在,则在字符串常量池中创建一个,并返回其地址值。
  • JDK1.7及之后:调用这个方法,先去字符串常量池中看是否已经存在,如果已经存在,那么直接返回这个常量在字符串常量池中的地址值,如果不存在,则在字符串常量池中保存一份堆中的引用,并返回其地址值。
    这也是JDK7对常量池保存内容的优化:通过字面量(常量)的方式和new String()的方式创建字符串,常量池保存的是对象;通过intern()方法,常量池保存的引用(一份堆中的引用的复制)。
    以下均在JDK7以后版本分析~

创建String的几种方式

  • String str= "abc":通过字面量的方式。先看常量池中是否已经存在相同字符串 (equals()),若存在,返回常量池中的引用。若不存在,在常量池中创建字符串对象并返回其引用。
  • String str = new String("abc"):通过显式创建字符串对象的方式。先在堆中创建一个对象,再看常量池中是否已经存在相同字符串,若存在,直接返回堆中的引用。若不存在,在常量池中创建字符串对象并返回堆中的引用。
  • String str = "abc" + "de":通过常量拼接的方式。会编译优化,等效于String str = "abcde"
  • String str = new String("abc") + new String("de"):涉及对象的拼接。底层是通过创建StringBuilder对象用append方法,最后通过toString转成String(只有new String()才会在常量池中创建对象)。共创建5个对象:堆中(“abc”,“de”,“adbde”),字符串常量池中(“abc”,“de”)。
    这也说明了,在循环体中无论用哪种形式拼接字符串,都会创建很多对象,浪费内存,影响性能~

通过4个例子完整分析intern()影响的内存结构

System.identityHashCode(obj)表示对象引用地址的 hashcode。
常量池逻辑属于方法区,物理上存在于堆中,为方便描述下面不予区分了。

  • eg1
    @Test
    public void function1() {
        String str1 = new String("abc");
        String str2 = "abc";
        String intern = str1.intern();
        System.out.println(str1==str2);//false
        System.out.println("str1:   "+System.identityHashCode(str1));//1018081122
        System.out.println("str2:   "+System.identityHashCode(str2));//242131142
        System.out.println("intern: "+System.identityHashCode(intern));//242131142
    }

从JVM看String及intern方法
String str1 = new String("abc")在堆和常量池各创建一个对象,str1指向堆。
String str2 = "abc"在常量池中找到了"abc"的引用,所以直接返回常量池的引用。
String intern = str1.intern()在常量池中找到了"abc"的引用,所以直接返回常量池的引用。

  • eg2
    @Test
    public void function2() {
        String str1 = new String("abc");
        String intern = str1.intern();
        String str2 = "abc";
        System.out.println(str1==str2);//false
        System.out.println("str1:   "+System.identityHashCode(str1));//1018081122
        System.out.println("str2:   "+System.identityHashCode(str2));//242131142
        System.out.println("intern: "+System.identityHashCode(intern));//242131142
    }

这里只是改变了第2,3行顺序,但结构同eg2完全一样。

  • eg3
    @Test
    public void function3() {
//        String hel = new String("hel");
//        String lo = new String("lo");
//        String str1 = hel+lo;
        String str1 = new String("hel") + new String("lo");
        String intern = str1.intern();
        String str2 = "hello";
        System.out.println(str1 == str2);//true
        System.out.println("str1:   " + System.identityHashCode(str1));//1018081122
        System.out.println("str2:   " + System.identityHashCode(str2));//1018081122
        System.out.println("intern: " + System.identityHashCode(intern));//1018081122
//        System.out.println(System.identityHashCode(hel));//242131142
//        System.out.println(System.identityHashCode(hel.intern()));//1782113663
//        System.out.println(System.identityHashCode(lo));//1433867275
//        System.out.println(System.identityHashCode(lo.intern()));//476800120
    }

释放注释,则可打印详细的对象的内存地址hashcode。
从JVM看String及intern方法
String str1 = new String("hel") + new String("lo");共创建5个对象:堆中(“hel”,“lo”,“hello”),字符串常量池中(“hel”,“lo”)。str1指向堆。
String intern = str1.intern();在常量池中没找到"hello"的引用,所以在常量池中保存一份hello对象的引用,并返回该引用地址。
String str2 = "hello";在常量池中找到了"abc"的引用,所以直接返回常量池的引用。
str1指向堆的引用,intern 和str2指向的是str1的堆中引用的复制品,所以都是相等的。

  • eg4
    改变了第2,3行顺序
    @Test
    public void function4() {
//        String hel = new String("hel");
//        String lo = new String("lo");
//        String str1 = hel+lo;
        String str1 = new String("hel") + new String("lo");
        String str2 = "hello";
        String intern = str1.intern();
        System.out.println(str1 == str2);//true
        System.out.println("str1:   " + System.identityHashCode(str1));//1018081122
        System.out.println("str2:   " + System.identityHashCode(str2));//242131142
        System.out.println("intern: " + System.identityHashCode(intern));//242131142
//        System.out.println(System.identityHashCode(hel));//242131142
//        System.out.println(System.identityHashCode(hel.intern()));//1782113663
//        System.out.println(System.identityHashCode(lo));//1433867275
//        System.out.println(System.identityHashCode(lo.intern()));//476800120
    }

从JVM看String及intern方法
String str1 = new String("hel") + new String("lo");先创建5个对象:堆中(“hel”,“lo”,“hello”),字符串常量池中(“hel”,“lo”)。str1指向堆。
String str2 = "hello";在常量池中没找到了"abc"的引用,所以创建对象并返回常量池的引用。
String intern = str1.intern();在常量池中找到了"hello"的引用,所以直接返回该引用地址。
str1指向堆的引用,intern 和str2指向的是str1的常量池中的引用,所以str1和str2不相等。

String随着JDK做的改变

JDK1.6:常量池存在于PermGen区(永久代)。缺点:永久代大小不好指定。与Java堆物理隔离,intern()可能产生对个重复的字符串,浪费性能。
JDK1.7:常量池存在于堆中。优点:堆大小不再受于固定大小。位于堆区的常量池,可以被垃圾回收。字符串常量池内部维护一个HashMap,通过需要intern()数量的2倍设置为size(减少hash冲突)。性能更好。
JDK1.8 :存储数据结构char[] 。
JDK1.9 :存储数据结构byte[]。动机:大多数String存的是英文,iso-8859等,只有一个字节就够了(char是两个字节),优化为byte[],如果是中文再用两个字节。

两个问题

大家看到最后应该对字符串常量池及intern方法有了一定的理解。但是这里还有两个问题:
1.在eg3的例子中如果字符串是"java"、"12"、"1122"等一些特殊字符串例子就不成立了。猜测:jvm初始化的时候常量池中就已经存在这些字符串了。
2.通过单测和主线程运行同样代码,结果不一样。查阅后,什么魔法值?

    public static void main(String[] args) {
        String s3 = new String("1") + new String("1");
        s3.intern();
        String s4 = "11";
        System.out.println(s3 == s4);//true
    }

    @Test
    public void fun4() {
        String s3 = new String("1") + new String("1");
        s3.intern();
        String s4 = "11";
        System.out.println(s3 == s4);//false
    }

到这实在不懂了,渴望分享交流,有兴趣的可以研究一下。

写到最后的感受

从我遇到intern方法想弄懂那刻开始,这期间我查阅了好多,发现言论都不一样,如:字符串常量池中保存的引用还是对象、String str = new String("abc")是创建几个对象、String str = new String("abc") + new String("de")常量池中到底有没有str等。
就这些技术而言,不是很深入的东西一查一大堆,而更加深入一点的问题,就不容易找到答案了。技术的道路是孤独的,这是很平常的事,做技术的人应该有一颗“想要弄懂”的心,然后才能发现和创造。最后,祝大家在技术的路上越走越远,对自己人生追求的事物越走越近!
本文不足的地方,欢迎指正和交流~

本文地址:https://blog.csdn.net/weixin_42476498/article/details/107349701