乐趣区

关于java:JVM系列之Stringintern和stringTable

简介

StringTable 是什么?它和 String.intern 有什么关系呢?在字符串对象的创立过程中,StringTable 有起到了什么作用呢?

所有的答案都在本文中,快来看看吧。

intern 简介

intern 是 String 类中的一个 native 办法,所以它底层是用 c ++ 来实现的。感兴趣的同学能够去查看下 JVM 的源码理解更多的内容。

这里咱们次要谈一下 intern 的作用。

intern 返回的是这个 String 所代表的对象,怎么了解呢?

String class 保护了一个公有的 String pool, 这个 String pool 也叫 StringTable, 中文名字叫做字符串常量池。

当咱们调用 intern 办法的时候,如果这个 StringTable 中曾经蕴含了一个雷同的 String 对象(依据 equals(Object)办法来判断两个 String 对象是否相等),那么将会间接返回保留在这个 StringTable 中的 String。

如果 StringTable 中没有雷同的对象,那么这个 String 对象将会被退出 StringTable,并返回这个 String 对象的援用。

所以,当且仅当 s.equals(t) 的时候 s.intern() == t.intern()。

intern 和字符串字面量常量

咱们晓得在类文件被编译成 class 文件时,每个 class 文件都有一个常量池,常量池中存了些什么货色呢?

字符串常量,类和接口名字,字段名,和其余一些在 class 中援用的常量。

看一个非常简单的 java 类:

public class SimpleString {public String site="www.flydean.com";}

而后看一下编译进去的 class 文件中的 Constant Pool:

Constant pool:
   #1 = Methodref          #2.#3          // java/lang/Object."<init>":()V
   #2 = Class              #4             // java/lang/Object
   #3 = NameAndType        #5:#6          // "<init>":()V
   #4 = Utf8               java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = String             #8             // www.flydean.com
   #8 = Utf8               www.flydean.com
   #9 = Fieldref           #10.#11        // com/flydean/SimpleString.site:Ljava/lang/String;
  #10 = Class              #12            // com/flydean/SimpleString
  #11 = NameAndType        #13:#14        // site:Ljava/lang/String;
  #12 = Utf8               com/flydean/SimpleString
  #13 = Utf8               site
  #14 = Utf8               Ljava/lang/String;
  #15 = Utf8               Code
  #16 = Utf8               LineNumberTable
  #17 = Utf8               LocalVariableTable
  #18 = Utf8               this
  #19 = Utf8               Lcom/flydean/SimpleString;
  #20 = Utf8               SourceFile
  #21 = Utf8               SimpleString.java

下面的后果,咱们能够看到 class 常量池中的 index 7 寄存了一个字符串,这个字符串的理论内容寄存在 index 8 中,是一个变种的 Utf8 的编码。

   #7 = String             #8             // www.flydean.com
   #8 = Utf8               www.flydean.com

好了,当初问题来了,class 文件中的常量池在运行时须要转换成为 JVM 可能辨认的运行时常量池,这个运行时的常量池和 StringTable 和 intern 有什么关系呢?

在 java 对象的实例化过程中,所有的字符串字面量都会在实例化的时候主动调用 intern 办法。

如果是第一次调用,则会创立新的 String 对象,寄存在 String Table 中,并返回该 String 对象的援用。

剖析 intern 返回的 String 对象

从下面的图中,咱们也能够进去 String Table 中存储的是一个 String 对象,它和一般的 String 对象没有什么区别,也分为对象头,底层的 byte 数组援用,int hash 值等。

如果你不置信,能够应用 JOL 来进行剖析:

log.info("{}", ClassLayout.parseInstance("www.flydean.com".intern()).toPrintable());

看下输入后果:

INFO com.flydean.StringInternJOL - java.lang.String object internals:
 OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
      0     4           (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)
      4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4           (object header)                           77 1a 06 00 (01110111 00011010 00000110 00000000) (399991)
     12     4    byte[] String.value                              [119, 119, 119, 46, 102, 108, 121, 100, 101, 97, 110, 46, 99, 111, 109]
     16     4       int String.hash                               0
     20     1      byte String.coder                              0
     21     1   boolean String.hashIsZero                         false
     22     2           (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 2 bytes external = 2 bytes total

剖析理论的问题

有了下面的常识,让咱们剖析一下上面的理论问题吧:

        String a =new String(new char[]{'a','b','c'});
        String b = a.intern();
        System.out.println(a == b);

        String x =new String("def");
        String y = x.intern();
        System.out.println(x == y);

两个很简略的例子,答案是什么呢?答案是 true 和 false。

第一个例子依照下面的原理很好了解,在构建 String a 的时候,String table 中并没有”abc“这个字符串实例。所以 intern 办法会将该对象增加到 String table 中,并返回该对象的援用。

所以 a 和 b 其实是一个对象,返回 true。

那么第二个例子呢?初始化 String 的时候,不是也没有”def“这个字符串吗?为什么回返回 false 呢?

还记得咱们下面一个大节剖析的吗?所有的字符串字面量在初始化的时候会默认调用 intern 办法。

也就是说”def“在初始化的时候,曾经调用了一次 intern 了,这个时候 String table 中曾经有”def“这个 String 了。

所以 x 和 y 是两个不同的对象,返回的是 false。

留神,下面的例子是在 JDK7+ 之后运行的,如果你是在 JDK6 中运行,那么失去的后果都是 false。

JDK6 和 JDK7 有什么不同呢?

在 JDK6 中,StringTable 是寄存在办法区中的,而办法区是放在永恒代中的。每次调用 intern 办法,如果 String Table 中不存在该 String 对象,则会将该 String 对象进行一次拷贝,并返回拷贝后 String 对象的援用。

因为做了一次拷贝,所以援用的不是同一个对象了。后果为 false。

在 JDK7 之后,StringTable 曾经被转移到了 java Heap 中了,调用 intern 办法的时候,StringTable 能够间接将该 String 对象退出 StringTable,从而指向的是同一个对象。

G1 中的去重性能

如果频繁的进行 String 的复制,实际上是十分耗费内存空间的。所以在 G1 垃圾回收器中,能够应用上面的:

-XX:+UseStringDeduplication

来开启 String 的去重性能。

咱们还记得 String 对象的底层构造吧,就是一个 byte[] 数组,String 去重的原理就是让多个字符串对象底层的 byte 数组指向同一个中央。从而节俭内存。

咱们能够通过应用:

-XX:+PrintStringTableStatistics

参数来查看 StringTable 的大小。并通过:

-XX:StringTableSizen=n

来指定 StringTable 的大小。

总结

本文讲了 String.intern 和 String table 的关系,如果有什么谬误或者脱漏的中央,欢送大家留言给我!

本文作者:flydean 程序那些事

本文链接:http://www.flydean.com/jvm-string-intern/

本文起源:flydean 的博客

欢送关注我的公众号: 程序那些事,更多精彩等着您!

退出移动版