前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java中String.intern的作用及适用场景

Java中String.intern的作用及适用场景

作者头像
KINGYT
发布2023-03-15 13:50:18
3070
发布2023-03-15 13:50:18
举报

本文将从源码角度分析String.intern方法的作用及其适用场景。OpenJDK版本

➜ jdk hg id 76072a077ee1+ jdk-11+28

首先,我们来看下该方法的Javadoc文档

代码语言:javascript
复制
/**
 * Returns a canonical representation for the string object.
 * <p>
 * A pool of strings, initially empty, is maintained privately by the
 * class {@code String}.
 * <p>
 * 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.
 * <p>
 * It follows that for any two strings {@code s} and {@code t},
 * {@code s.intern() == t.intern()} is {@code true}
 * if and only if {@code s.equals(t)} is {@code true}.
 * <p>
 * All literal strings and string-valued constant expressions are
 * interned. String literals are defined in section 3.10.5 of the
 * <cite>The Java&trade; Language Specification</cite>.
 *
 * @return  a string that has the same contents as this string, but is
 *          guaranteed to be from a pool of unique strings.
 * @jls 3.10.5 String Literals
 */
public native String intern();

其实上面文档说的还是比较清楚的,当该方法被调用时,如果JVM内部维护的string pool中已经存在和这个string内容相同的string,就返回pool中的string,否则的话,就会先把这个string放入pool中,然后再返回这个string。

不过,为了加深对该方法的理解,我们还是从源码角度再看下。

由上可知,intern是个native方法,所以我们要先找到这个方法对应的C代码

C文件src/java.base/share/native/libjava/String.c

代码语言:javascript
复制
JNIEXPORT jobject JNICALL
Java_java_lang_String_intern(JNIEnv *env, jobject this)
{
    return JVM_InternString(env, this);
}

该方法调用了JVM_InternString,看下这个方法

C++文件src/hotspot/share/prims/jvm.cpp

代码语言:javascript
复制
JVM_ENTRY(jstring, JVM_InternString(JNIEnv *env, jstring str))
  ...
  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方法

C++文件src/hotspot/share/classfile/stringTable.cpp

代码语言:javascript
复制
oop StringTable::intern(oop string, TRAPS) {
  if (string == NULL) return NULL;
  ...
  Handle h_string (THREAD, string);
  jchar* chars = java_lang_String::as_unicode_string(string, length,
                                                     CHECK_NULL);
  oop result = intern(h_string, chars, length, CHECK_NULL);
  return result;
}
...
oop StringTable::intern(Handle string_or_null_h, jchar* name, int len, TRAPS) {
  ...
  unsigned int hash = java_lang_String::hash_code(name, len);
  ...
  return StringTable::the_table()->do_intern(string_or_null_h, name, len,
                                             hash, CHECK_NULL);
}

继续看下StringTable::the_table()->do_intern方法

C++文件src/hotspot/share/classfile/stringTable.cpp

代码语言:javascript
复制
oop StringTable::do_intern(Handle string_or_null_h, jchar* name,
                           int len, uintx hash, TRAPS) {
  ...
  StringTableLookupOop lookup(THREAD, hash, string_h);
  StringTableCreateEntry stc(THREAD, string_h);
  ...
  _local_table->get_insert_lazy(THREAD, lookup, stc, stc, &rehash_warning);
  ...
  return stc.get_return();
}

该方法中,_local_table就是JVM内部维护的string pool,其类型类似与Java中的ConcurrentHashMap。

StringTableLookupOop类是用于查询_local_table中是否存在对应的string。

StringTableCreateEntry类的作用是,当_local_table中不存在对应string时,用它来创建一个存放着新string的WeakHandle,之后这个WeakHandle会被添加到_local_table中。

StringTableCreateEntry类还有另外一个作用,就是用于接收最终的结果string,不管这个string是新创建的,还是原来就存在的。这也是为什么get_insert_lazy方法第四个参数还是stc的原因。

最后,StringTable::do_intern方法调用stc.get_return()返回结果,即,如果有对应的string,则返回对应的string,如果没有,则返回原string。

我们再来看下StringTableLookupOop和StringTableCreateEntry这两个类的实现

C++文件src/hotspot/share/classfile/stringTable.cpp

代码语言:javascript
复制
class StringTableLookupOop : public StackObj {
 private:
  Thread* _thread;
  uintx _hash;
  Handle _find;
  ...
 public:
  StringTableLookupOop(Thread* thread, uintx hash, Handle handle)
    : _thread(thread), _hash(hash), _find(handle) { }
  ...
  bool equals(WeakHandle<vm_string_table_data>* value, bool* is_dead) {
    oop val_oop = value->peek();
    ...
    bool equals = java_lang_String::equals(_find(), val_oop);
    if (!equals) {
      return false;
    }
    ...
    return true;
  }
};

上面equals方法就是用来检测_local_table中的string是否就是我们想要的string。

代码语言:javascript
复制
class StringTableCreateEntry : public StackObj {
 private:
   Thread* _thread;
   Handle  _return;
   Handle  _store;
 public:
  StringTableCreateEntry(Thread* thread, Handle store)
    : _thread(thread), _store(store) {}


  WeakHandle<vm_string_table_data> operator()() { // No dups found
    WeakHandle<vm_string_table_data> wh =
      WeakHandle<vm_string_table_data>::create(_store);
    return wh;
  }
  void operator()(bool inserted, WeakHandle<vm_string_table_data>* val) {
    oop result = val->resolve();
    assert(result != NULL, "Result should be reachable");
    _return = Handle(_thread, result);
  }
  oop get_return() const {
    return _return();
  }
};

上面的第一个operator()方法就是用来创建_local_table中的WeakHandle,来存放新的string。第二个operator()方法就是用来接收最终结果,其中inserted参数用来表示这个string是否是新添加的。

至此,源码分析就结束了。

结合上面的Javadoc文档和源码分析,我们来想下,String.intern方法的适用场景究竟是什么呢?

首先,从Javadoc文档中我们可以得知,literal string本身就已经被intern了,所以intern方法对动态创建的string才有意义。

其次,从源码中我们可以看到,intern方法的逻辑还是比较复杂的,所以对于它返回的结果,我们不应该是立即使用,然后就丢弃,这样得不偿失。

最后,由intern本身的机制我们可以得知,调用intern方法的string必须是大量重复的,否则也没有意义。

对于这些限制条件,我第一想到的适用场景是,从网络接收的string,且这些string是提前预设的ID,且这些string会派发到其他线程做后续处理。这个场景也基本满足了动态创建、延迟使用、大量重复等特性。不过之后我做了些性能测试,发现intern的性能比我预想的要差很多,所以这种场景也不太适用了。

最后总结下,intern方法主要用于那些,动态创建的,会较长时间存活的,最好是会多次使用的,且存在大量重复的string。并且,调用intern方法的代码段对性能没有非常严格的要求。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-01-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 卯时卯刻 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档