关闭

Java (JDK7)中的String常量和String.intern的实现

标签: stringstringtable常量池intern方法
6915人阅读 评论(4) 收藏 举报
分类:

在java中有constantPool常量池,常量池里存放的是类,方法,接口的等常量,而对于存放字符串常量通常存放的符号链接Symbol 或者真实的String的对象的引用。

我们来看一段简单的代码和反编译字节码

public class test {
	public static void main(String[] args) {
		String test = "test";
	}

}


Constant pool:
   #1 = Class              #2             //  test
   #2 = Utf8               test
   #3 = Class              #4             //  java/lang/Object
   #4 = Utf8               java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Utf8               Code
   #8 = Methodref          #3.#9          //  java/lang/Object."<init>":()V
   #9 = NameAndType        #5:#6          //  "<init>":()V
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Ltest;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = String             #2             //  test
  #17 = Utf8               args
  #18 = Utf8               [Ljava/lang/String;
  #19 = Utf8               Ljava/lang/String;
  #20 = Utf8               SourceFile
  #21 = Utf8               test.java
{
  public test();
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0       
         1: invokespecial #8                  // Method java/lang/Object."<init>":()V
         4: return        
      LineNumberTable:
        line 2: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0       5     0  this   Ltest;

  public static void main(java.lang.String[]);
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=2, args_size=1
         0: ldc           #16                 // String test
         2: astore_1      
         3: return        
      LineNumberTable:
        line 6: 0
        line 15: 3
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0       4     0  args   [Ljava/lang/String;
               3       1     1  test   Ljava/lang/String;
}

在反编译字节码中,我们看到了常量池的定义

#16 = String  #2

#2 =utf8 test

当这个字符串常量内容test在类初始化的时候是以符号链接Symbol存放,存放的是UTF-8编码的c里的char数组,存放的索引是在#16而不是#2,这在类的初始化的时候已经直接关联好了。

对于 String test="test" 代码所对应的调用指令

0: ldc  #16

2: astore_1

可以看到一个语句拆成了2个部分,一个是ldc #16 和保存引用到参数test

那我们来看看ldc指令是如何执行的,在interpreterRuntime.cpp中我们看到了ldc的执行

IRT_ENTRY(void, InterpreterRuntime::ldc(JavaThread* thread, bool wide))
  // access constant pool
  constantPoolOop pool = method(thread)->constants();
  int index = wide ? get_index_u2(thread, Bytecodes::_ldc_w) : get_index_u1(thread, Bytecodes::_ldc);
  constantTag tag = pool->tag_at(index);

  if (tag.is_unresolved_klass() || tag.is_klass()) {
    klassOop klass = pool->klass_at(index, CHECK);
    oop java_class = klass->java_mirror();
    thread->set_vm_result(java_class);
  } else {
#ifdef ASSERT
    // If we entered this runtime routine, we believed the tag contained
    // an unresolved string, an unresolved class or a resolved class.
    // However, another thread could have resolved the unresolved string
    // or class by the time we go there.
    assert(tag.is_unresolved_string()|| tag.is_string(), "expected string");
#endif
    oop s_oop = pool->string_at(index, CHECK);
    thread->set_vm_result(s_oop);
  }
IRT_END

因为这是个字符串常量,代码调用了pool->string_at(index, CHECK) ,最后代码调用了string_at_impl方法

oop constantPoolOopDesc::string_at_impl(constantPoolHandle this_oop, int which, TRAPS) {
  oop str = NULL;
  CPSlot entry = this_oop->slot_at(which);
  if (entry.is_metadata()) {
    ObjectLocker ol(this_oop, THREAD);
    if (this_oop->tag_at(which).is_unresolved_string()) {
      // Intern string
      Symbol* sym = this_oop->unresolved_string_at(which);
      str = StringTable::intern(sym, CHECK_(constantPoolOop(NULL)));
      this_oop->string_at_put(which, str);
    } else {
      // Another thread beat us and interned string, read string from constant pool
      str = this_oop->resolved_string_at(which);
    }
  } else {
    str = entry.get_oop();
  }
  assert(java_lang_String::is_instance(str), "must be string");
  return str;
}

在代码中,我们可以看到在没有调用ldc 之前,字符串常量值是用symbol 来表示的,而当调用ldc之后,通过调用StringTable::intern产生了String的引用,并且存放在常量池中。如果在调用ldc指令的话,直接从常量池根据索引#16中取出String的引用(this_oop->resolved_string_at(which)),而避免再次从StringTable中去查找一次。

StringTable不是常量池

StringTable存放的是string的cache table, 用于存放字符串常量的引用的表,避免产生新的string的开销。 

StringTable数据结构是我们常用的java中的hashtable, 先计算字符串的hashcode,根据hashcode到对应的数组,然后遍历里面的链表结构比较字符串里的每个字符,直到找到相同的。当数据比较多的时候,会导致查找效率变慢,java会在进入safepoint点的时候判断是否需要做一次rehash,就是扩大数组的容量来提高查找的效率。


在调用ldc指令后,会把symbol 的c++ char 数组转化成新的unicode的java char 数组,并生成新的string的引用,将这个引用保存到StringTable中,当然同时这个引用也保存到了常量池中。


String.intern方法

String.intern()的方法原理是通过找到字符串所在Stringtable里保存的引用,代码如下

JVM_ENTRY(jstring, JVM_InternString(JNIEnv *env, jstring str))
  JVMWrapper("JVM_InternString");
  JvmtiVMObjectAllocEventCollector oam;
  if (str == NULL) return NULL;
  oop string = JNIHandles::resolve_non_null(str);
  oop result = StringTable::intern(string, CHECK_NULL);
  return (jstring) JNIHandles::make_local(env, result);
JVM_END
我们又看到了熟悉的StringTable:intern 的方法,而在这里和ldc有点不同的是,这时候引用已经存在,如果StringTable里不存在这个字符串的时候,会直接将该String的引用存放到StringTable中。


释疑

前段时间看到有博客提到了这句话使用String.intern()方法则可以将一个String类的保存到一个全局String表中,如果具有相同值的Unicode字符串已经在这个表中,那么该方法返回表中已有字符串的地址,如果在表中没有相同值的字符串,则将自己的地址注册到表中”,博客中阐述这个解释是错误的,同时举了例子

String s1=new String("kvill");        
System.out.println( s1==s1.intern() );  

但实际上这个例子举的是错误的。我们来看字节码。

0: new           #16                 // class java/lang/String
         3: dup           
         4: ldc           #18                 // String kvill
         6: invokespecial #20                 // Method java/lang/String."<init>":(Ljava/lang/String;)V
         9: astore_1     

我们看到了什么?ldc指令,也就是说"kvill" 这句话已经在常量池中生成了String的引用,而代码实际已经等同于

String test="kvill";
String s1 = new String(test);
这样的代码就可以比较清楚的看到这已经是完全两个不同的String对象了

而要证明原话是否正确,只要将程序改成

char[] test={'k','v','i','l','l'};
String s1=new String(test);
System.out.println(s1==s1.intern());
我们可以清楚的看到返回的结果是true,也就是说Stringtable里保存的是s1这个引用。















3
0
查看评论

java 19:不可变字符串和限定字符串(Immutable String and Interned String)

1 String 的构造 Astringis a sequence of characters. In many languages, strings are treated as an array of characters, but in Java a string is an object....
  • menogen
  • menogen
  • 2014-07-02 20:45
  • 1356

java6,7,8中String.intern进化史与深度剖析

这篇文章将要讨论 Java 6 中是如何实现 String.intern 方法的,以及这个方法在 Java 7 以及 Java 8 中做了哪些调整。 字符串池 字符串池(有名字符串标准化)是通过使用唯一的共享 String 对象来使用相同的值不同的地址表示字符...
  • chenleixing
  • chenleixing
  • 2015-05-14 08:48
  • 1930

java代码性能提高技巧之String.intern()

这篇文章将要讨论 Java 6 中是如何实现 String.intern 方法的,以及这个方法在 Java 7 以及 Java 8 中做了哪些调整。 字符串池      字符串池(有名字符串标准化)是通过使用唯一的共享 S...
  • zhushuai1221
  • zhushuai1221
  • 2016-09-25 20:00
  • 451

Java技术——你真的了解String类的intern()方法吗

说实话我本来想总结一篇Android内存泄漏的文章的,查阅了很多资料,发现不得不从Java的OOM讲起,讲Java的OOM又不得不讲Java的虚拟机架构。在JVM架构一文中也有介绍,在JVM运行时数据区中的方法区有一个常量池,但是发现在JDK1.6以后常量池被放置在了堆空间,因此常量池位置的不同影响...
  • SEU_Calvin
  • SEU_Calvin
  • 2016-08-23 16:40
  • 29765

JDK7与JDK6中String.Intern方法的区别

http://tech.meituan.com/in_depth_understanding_string_intern.html
  • yyd19921214
  • yyd19921214
  • 2017-05-30 15:44
  • 368

Java技术——你真的了解String类的intern()方法吗

0.引言 什么都先不说,先看下面这个引入的例子: [java] view plain copy String str1 = new String("SEU")+ new Stri...
  • baidu_31657889
  • baidu_31657889
  • 2016-08-25 16:30
  • 9470

java jdk1.7常量池移到哪去了?

今天模拟了一下常量池的oom,突然发现设置的参数-XX:PermSize=10M -XX:MaxPermSize=10M不管用了,同时发现内存一直在上升,当上升到一个极值就会趋于平稳,然后再过一段时间会报: Exception in thread “main” java.lang.OutOfMem...
  • u014039577
  • u014039577
  • 2015-12-22 09:38
  • 5949

String的intern()方法详解

官方API: intern public String intern() 返回字符串对象的规范化表示形式。 一个初始时为空的字符串池,它由类 String 私有地维护。 当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(该对象由 equals(Object)...
  • wjzhang5514
  • wjzhang5514
  • 2017-04-17 11:42
  • 358

关于java String的intern()方法的理解

1.首先先来看这样一个问题: String s1 = new String (“test”); String s2 = new String(“test”); 在这个过程中,创建了几个String对象? 答案是3个:一个test常量在常量池中,两个在堆中,对应的引用分别为s1和s2. 参考...
  • weizi2016
  • weizi2016
  • 2017-02-15 19:37
  • 102

关于java中String的 intern()方法

String对象是java语言中重要的数据类型,但它并不是java的基本数据类型。在C语言中,对字符串的处理是使用char数组,但这种使用方式非常麻烦。Java语言中,String对象可以认为是对插入数组的进一步封装,它主要由3部分组成:char数组、偏移量和String长度。String的真实内容...
  • zsh2050
  • zsh2050
  • 2016-08-21 23:26
  • 217
    个人资料
    • 访问:499194次
    • 积分:5443
    • 等级:
    • 排名:第5864名
    • 原创:100篇
    • 转载:3篇
    • 译文:0篇
    • 评论:73条
    最新评论