HotSpot String常量池

HotSpot中使用StringTable来缓存字符串常量,以提高程序的运行性能。在Java语言中String类被final关键字修饰,意味着String类不能被继承,并且它的成员方法都默认为final方法;字符串一旦创建就不能再修改。String实例的值是通过字符数组实现字符串存储的。使用字符串池的优点就是避免了相同内容的字符串的创建,节省了内存,省去了创建相同字符串的时间,同时提升了性能;另一方面,字符串池的缺点就是牺牲了JVM在常量池中遍历对象所需要的时间,不过其时间成本相比而言比较低。

一.StringTable

HotSpot内部StringTable是一个Hash表

public class Neo {
    public static void main(String[] args) {
        String neo = "the one";
        String murphys = "Master";
    }
}

使用javap -v Neo.class 查看字节码使用了ldc字节码指令

	0: ldc           #2                  // String the one
    2: astore_1
    3: ldc           #3                  // String Master
    5: astore_2
    6: return

在字节码解释器中ldc指令根据常量池tag类型来判断常量类型,对于JVM_CONSTANT_String类型,调用resolve_ldc方法来处理
src/share/vm/interpreter/bytecodeInterpreter.cpp

  CASE(_ldc):
        {
          ......
          ConstantPool* constants = METHOD->constants();
          switch (constants->tag_at(index).value()) {
    	  ......
          case JVM_CONSTANT_String:
            {
              oop result = constants->resolved_references()->obj_at(index);
              CALL_VM(InterpreterRuntime::resolve_ldc(THREAD, (Bytecodes::Code) opcode), handle_exception);
                THREAD->set_vm_result(NULL);
            break;
           }

src/share/vm/interpreter/interpreterRuntime.cpp

IRT_ENTRY(void, InterpreterRuntime::resolve_ldc(JavaThread* thread, Bytecodes::Code bytecode)) {
  ResourceMark rm(thread);
  methodHandle m (thread, method(thread));
  Bytecode_loadconstant ldc(m, bci(thread));
  oop result = ldc.resolve_constant(CHECK);
#endif
  thread->set_vm_result(result);
}
IRT_END

src/share/vm/interpreter/bytecode.cpp

oop Bytecode_loadconstant::resolve_constant(TRAPS) const {
  int index = raw_index();
  ConstantPool* constants = _method->constants();
  if (has_cache_index()) {
    return constants->resolve_cached_constant_at(index, THREAD);
  } else {
    return constants->resolve_constant_at(index, THREAD);
  }
}

src/share/vm/oops/constantPool.hpp

oop resolve_cached_constant_at(int cache_index, TRAPS) {
    constantPoolHandle h_this(THREAD, this);
    return resolve_constant_at_impl(h_this, _no_index_sentinel, cache_index, THREAD);
  }

src/share/vm/oops/constantPool.cpp

oop ConstantPool::resolve_constant_at_impl(const constantPoolHandle& this_cp, 
											int index, int cache_index, TRAPS) {
 constantTag tag = this_cp->tag_at(index);

  switch (tag.value()) {
  case JVM_CONSTANT_String:
    //
    result_oop = string_at_impl(this_cp, index, cache_index, CHECK_NULL);
    break;
  }
}

src/share/vm/oops/constantPool.cpp

oop ConstantPool::string_at_impl(const constantPoolHandle& this_cp, int which, int obj_index, TRAPS) {
  // 如果字符串已被截取,则此项将为非空
  oop str = this_cp->resolved_references()->obj_at(obj_index);
  if (str != NULL) return str;
  //如果为空,则重新生成符号
  Symbol* sym = this_cp->unresolved_string_at(which);
  //没错,放到字符串常量表中
  str = StringTable::intern(sym, CHECK_(NULL));
  this_cp->string_at_put(which, obj_index, str);
  return str;
}

intern函数会首先从StringTable中查找是否存在相同的字符串常量,存在则直接取回,不存在则重新生成一个同时放到StringTable中
src/share/vm/classfile/stringTable.cpp

oop StringTable::intern(Handle string_or_null, jchar* name,
                        int len, TRAPS) {
  //生成hash码从共享表中找
  unsigned int hashValue = java_lang_String::hash_code(name, len);
  oop found_string = lookup_shared(name, len, hashValue);
  if (found_string != NULL) {
    return found_string;
  }
  if (use_alternate_hashcode()) {
    hashValue = alt_hash_string(name, len);
  }
  //从主表中找
  int index = the_table()->hash_to_index(hashValue);
  found_string = the_table()->lookup_in_main_table(index, name, len, hashValue);

  // Found
  if (found_string != NULL) {
    if (found_string != string_or_null()) {
      ensure_string_alive(found_string);
    }
    return found_string;
  }

  Handle string;
  // try to reuse the string if possible
  if (!string_or_null.is_null()) {
    string = string_or_null;
  } else {
    string = java_lang_String::create_from_unicode(name, len, CHECK_NULL);
  }

  //往表里新增一个
  oop added_or_found;
  {
    MutexLocker ml(StringTable_lock, THREAD);
    // Otherwise, add to symbol to table
    added_or_found = the_table()->basic_add(index, string, name, len,
                                  hashValue, CHECK_NULL);
  }
  return added_or_found;
}

对于String类中的intern方法,同样调用 StringTable::intern,intern 方法是一个native方法,intern方法会从字符串常量池中查询当前字符串是否存在,如果存在,就直接返回当前字符串;如果不存在就会将当前字符串放入常量池中,之后再返回。
java/lang/String.java

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
	public native String intern();
}

src/share/vm/prims/jvm.cpp

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

二.诡异行为

在Java中,String是不可变字符串对象,StringBuilder和StringBuffer是可变字符串对象(其内部的字符数组长度可变)。String中的对象是不可变的,也就可以理解为常量,显然线程安全。StringBuffer 与 StringBuilder 中的方法和功能完全是等价的,只是StringBuffer 中的方法大都采用了synchronized 关键字进行修饰,因此是线程安全的,而 StringBuilder 没有这个修饰,可以被认为是非线程安全的。当字符串相加操作较多的情况下,建议使用StringBuilder,如果采用了多线程,则使用StringBuffer。在JDK11下String的比较表现出如下行为

public class Neo {
    public static void main(String[] args) {
        String s1 = "the one";
        String s2 = new String("the one");
        String s3 = s1.intern();
        String s4 = s2.intern();
        System.out.println(s1 == s2);        //false
        System.out.println(s1.equals(s2));   //true
        System.out.println(s1.equals(s3));   //true
        System.out.println(s1 == s3);		 //true
        System.out.println(s1 == s4);        //true
        System.out.println(s2 == s4);        //false
      
    }
}
  1. 对于s1 ==s2 为false,比较的是对象,即在HotSpot内部s1,s2指向两个不同的对象。
  2. 对于s1.equals(s2)为true,比较的是对象的内容
  3. 对于s1.equals(s3)为true,比较的是对象的内容
  4. 对于s1 ==s3为true,s3是由s1.intern()返回的,此时"the one"已在StringTable,虚拟机返回了s1的oop
  5. 对于s1 ==s4为true,s4是s2.intern()返回的,此时"the one"已在StringTable,虚拟机返回了s1的oop,没错是s1
  6. 对于s2 ==s4为false,s4是s2.intern()返回的,此时"the one"已在StringTable,虚拟机返回了s1的oop,没错是s1

上述创建了两个对象:s1,s2,对于s3,由于字符串常量已存在月StringTable中,并且是由s1存进去的。s1.intern()返回的是对s1的引用。对于s4,虽然它是s2.intern()返回的,但StringTable中的常量是由s1存进去的,所以s2.intern()返回的仍然是s1的引用,诡异得很。所以s4 != s2。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值