您当前的位置: 首页 > 

庄小焱

暂无认证

  • 3浏览

    0关注

    805博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

JDK源码——intern()方法和sun.misc.Version类

庄小焱 发布时间:2021-11-25 13:32:31 ,浏览量:3

摘要

本博文主要是介绍String类中Intern()方法。在JAVA语言中有基本类型和一种比较特殊的类型String。这些类型为了使他们在运行过程中速度更快,更节省内存,都提供了一种常量池的概念。常量池就类似一个JAVA系统级别提供的缓存。8种基本类型的常量池都是系统协调的,String类型的常量池比较特殊。它的主要使用方法有两种:1、直接使用双引号声明出来的String对象会直接存储在常量池中。2、如果不是用双引号声明的String对象,可以使用String提供的intern方法。intern 方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中,如果存在那就是直接使用内存池中存在的字符串变量。

在源码中涉及到sun.misc.Version类。sun.misc.Version类会在JDK类库的初始化过程中被加载并初始化,而在初始化时它需要对静态常量字段根据指定的常量值(ConstantValue)做默认初始化,此时被sun.misc.Vversion.launcher静态常量字段所引用的"java"字符串字面量就被intern到HotSpot VM的字符串常量池——StringTable里了。

一、Intern()定义

intern 方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中,如果存在那就是直接使用内存池中存在的字符串变量。

/** 
* JDk相关的Intern方法
 */ 
public native String intern();

native 代码:在 jdk7后,oracle 接管了 JAVA 的源码后就不对外开放了,根据 jdk 的主要开发人员声明 openJdk7 和 jdk7 使用的是同一分主代码,只是分支代码会有些许的变动。

jdk6 和 jdk7 下 intern 的区别

相信很多 JAVA 程序员都做做类似 String s = new String("abc")这个语句创建了几个对象的题目。 这种题目主要就是为了考察程序员对字符串对象的常量池掌握与否。上述的语句中是创建了2个对象,第一个对象是”abc”字符串存储在常量池中,第二个对象在JAVA Heap中的 String 对象。

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

-----------------------打印结果是---------------------------

jdk6 下false false
jdk7 下false true

然后将s3.intern();语句下调一行,放到String s4 = "11";后面。将s.intern(); 放到String s2 = "1";后面。是什么结果呢

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

-----------------------打印结果是---------------------------

jdk6 下false false
jdk7 下false false
1.1 jdk6中的解释

如上图所示。首先说一下 jdk6中的情况,在 jdk6中上述的所有打印都是 false 的,因为 jdk6中的常量池是放在 Perm 区中的,Perm区和正常的 JAVA Heap 区域是完全分开的。上面说过如果是使用引号声明的字符串都是会直接在字符串常量池中生成,而 new 出来的 String 对象是放在 JAVA Heap 区域。所以拿一个 JAVA Heap 区域的对象地址和字符串常量池的对象地址进行比较肯定是不相同的,即使调用String.intern方法也是没有任何关系的。

1.2 jdk7中的解释

在 Jdk6 以及以前的版本中,字符串的常量池是放在堆的Perm区的,Perm区是一个类静态的区域,主要存储一些加载类的信息,常量池,方法片段等内容,默认大小只有4m,一旦常量池中大量使用 intern 是会直接产生java.lang.OutOfMemoryError:PermGen space错误的。在 jdk7 的版本中,字符串常量池已经从Perm区移到正常的Java Heap区域了。为什么要移动,Perm 区域太小是一个主要原因,当然据消息称jdk8已经直接取消了Perm区域,而新建立了一个元区域。应该是jdk开发者认为Perm区域已经不适合现在 JAVA 的发展了。正式因为字符串常量池移动到JAVA Heap区域后,再来解释为什么会有上述的打印结果。

  • 在第一段代码中,先看 s3和s4字符串。String s3 = new String("1") + new String("1");,这句代码中现在生成了2最终个对象,是字符串常量池中的“1” 和 JAVA Heap中的 s3引用指向的对象。中间还有2个匿名的new String("1")我们不去讨论它们。此时s3引用对象内容是”11″,但此时常量池中是没有 “11”对象的。
  • 接下来s3.intern();这一句代码,是将 s3中的"11"字符串放入String 常量池中,因为此时常量池中不存在"11"字符串,因此常规做法是跟 jdk6 图中表示的那样,在常量池中生成一个"11"的对象,关键点是 jdk7 中常量池不在Perm区域了,这块做了调整。常量池中不需要再存储一份对象了,可以直接存储堆中的引用。这份引用指向s3引用的对象。 也就是说引用地址是相同的。
  • 最后String s4 = "11"; 这句代码中”11″是显示声明的,因此会直接去常量池中创建,创建的时候发现已经有这个对象了,此时也就是指向s3引用对象的一个引用。所以s4引用就指向和s3一样了。因此最后的比较 s3 == s4 是 true。
  • 再看s和 s2 对象。String s = new String("1"); 第一句代码,生成了2个对象。常量池中的“1” 和 JAVA Heap 中的字符串对象。s.intern(); 这一句是 s 对象去常量池中寻找后发现 “1” 已经在常量池里了。
  • 接下来String s2 = "1"; 这句代码是生成一个 s2的引用指向常量池中的“1”对象。 结果就是 s 和 s2 的引用地址明显不同。图中画的很清晰。

  • 来看第二段代码,从上边第二幅图中观察。第一段代码和第二段代码的改变就是 s3.intern(); 的顺序是放在String s4 = "11";后了。这样,首先执行String s4 = "11";声明 s4 的时候常量池中是不存在“11”对象的,执行完毕后,“11“对象是 s4 声明产生的新对象。然后再执行s3.intern();时,常量池中“11”对象已经存在了,因此 s3 和 s4 的引用是不同的。
  • 第二段代码中的 s 和 s2 代码中,s.intern();,这一句往后放也不会有什么影响了,因为对象池中在执行第一句代码String s = new String("1");的时候已经生成“1”对象了。下边的s2声明都是直接从常量池中取地址引用的。 s 和 s2 的引用地址是不会相等的。

从上述的例子代码可以看出 jdk7 版本对 intern 操作和常量池都做了一定的修改。主要包括2点:

  • 将String常量池从Perm区移动到了Java Heap区
  • String#intern 方法时,如果存在堆中的对象,会直接保存对象的引用,而不会重新创建对象。
二、在JDK1.8的情况下intern()方法

package com.zhuangxiaoyan.jdk.juc.JucLock;

/**
 * @Classname internDemo
 * @Description TODO
 * @Date 2021/11/28 8:51
 * @Created by xjl
 */
public class internDemo {

    public static void main(String[] args) {
        String s=new String("a");
        s.intern();
        String s2="a";
        System.out.println(s==s2);

        System.out.println("---------------------------------------------");

        String s3=new String("a")+new String("a");
        s3.intern();
        String s4="aa";
        System.out.println(s3==s4);

    }
}

-----------------------打印结果是---------------------------

false
---------------------------------------------
true
2.1 JDK6的解释

对于第一部分代码: "a"被放在字符串常量池中,然后(因为new)存放在堆中,s指向堆中的"a";[s.intern()]当把"a"副本放入字符串常量池时,发现有则不再放置,后面s2="a"则s2指向字符串常量池中的"a",因此s != s2。 对于第二部分代码:"a"被放在字符串常量池中,后来两个new出来的"a"相加,new出"aa"然后放在了堆中,s3指向堆中的"aa";[s3.intern()]当把"aa"副本放在字符串常量池中时,发现字符串常量池没有"aa",则放入(由于是副本则地址与堆中的不一样)。s4="aa"则s4指向字符串常量池中的"aa",因此s3 != s4。

2.2 JDK8的解释

对于第一部分代码: "a"被放在字符串常量池中,然后(因为new)存放在堆中,s指向堆中的"a";[s.intern()]当把"a"副本放入字符串常量池时,发现有则不再放置,后面s2="a"则s2指向字符串常量池中的"a",因此s != s2。

对于第二部分代码:"a"被放在字符串常量池中,后面new出来的"a"相加,可以(隐含)new出"aa"然后放在堆中,s3指向堆中的"aa";[s3.intern()]当把"aa"引用放在字符串常量池中时,发现没有则存入它的引用(此时与堆中的地址一样)。s4="aa"则s4指向字符串常量池中的"aa"(相当于指向堆中的"aa"),因此s3 == s4。

package com.example.jdk;

public class JDK_Test {
    public static void main(String[] args) {
        String str1=new StringBuilder("ali").append("haha").toString();
        System.out.println(str1);
        //首次遇到字符串示例复制到永久代的字符串常量池的存储 返回的是永久代中字符串示例的引用。
        System.out.println(str1.intern());
        System.out.println(str1==str1.intern());
        System.out.println("---------------------------------------------------------");
        String str2=new StringBuilder("1.8.0_").append("301").toString();
        System.out.println(str2);
        System.out.println(str2.intern());
        System.out.println(str2==str1.intern());
    }
}


结果显示

alihaha
alihaha
true
---------------------------------------------------------
java
java
false
三、JDK8的源码中sun.misc.Version类

以上值都是在JDK已经自带的。在加载时候sun.misc.version的类的时候就已经在存在常量池中。提示这个是openJDK的方法里面才有,其他的版本的中可能就么有相关的类。合理使用intern()确实有降低堆大小的作用,但是也会带来额外的系统开销。因此intern()需要根据场景进行使用,不可滥用。

博文参考

String的Intern方法详解 - 简单爱_wxg - 博客

关注
打赏
1657692713
查看更多评论
立即登录/注册

微信扫码登录

0.0417s