Java字符串存储在堆?还是字符串常量池?

本文详细探讨了Java 1.6和1.7中字符串常量池的实现,揭示了字符串.intern()方法的工作机制。在1.6中,字符串数据存储在永久代,而1.7及以后版本则存储在堆中。通过分析源码,证实了intern()方法在池中不存在相应字符串时会复制到相应区域,并返回指针。字符串常量池的结构允许存储数据或逻辑地址。文章还讨论了字符串常量池的数据结构及其高效存储的能力。

前言

先给出结论,不同点用红标

jdk1.6

(1)字符串数据存储在永久代,new出来的字符串数据存储在堆,字符串常量池仅存储指针数据

(2)new出来的字符串调用String.intern后:

       若字符串常量池中没有相应的数据,则堆中的字符串数据会被拷贝永久代,并返回字符串常量池中的指针;

       若字符串常量池中有相应的数据,直接返回字符串常量池中的指针

 

jdk1.7

(1)字符串数据存储在,new出来的字符串数据存储在堆,字符串常量池仅存储指针数据

(2)new出来的字符串调用String.intern后:

       若字符串常量池中没有相应的数据,则堆中的字符串数据的逻辑地址会被拷贝,并返回字符串常量池中的指针;

       若字符串常量池中有相应的数据,直接返回字符串常量池中的指针

 

上面大部分内容都跟《深入理解Java虚拟机》一致,唯一有疑惑的是intern的实现,所以下面仅证明intern

 

证明流程

 

以jdk1.6为例

(1)先按照openJDK之如何下载各个版本的openJDK源码找出相应版本的jdk源码

 

(2)找到String.intern的实现:http://hg.openjdk.java.net/jdk6/jdk6/hotspot/file/d9c3790c85c1/src/share/vm/prims/jvm.cpp

JVM_ENTRY(jstring, JVM_InternString(JNIEnv *env, jstring str))
  JVMWrapper("JVM_InternString");
  JvmtiVMObjectAllocEventCollector oam;
  if (str == NULL) return NULL; // 如果str为NULL,则直接返回NULL值
  oop string = JNIHandles::resolve_non_null(str);
  oop result = StringTable::intern(string, CHECK_NULL); // StringTable.intern实现具体逻辑
  return (jstring) JNIHandles::make_local(env, result);
JVM_END

 

(3)再查看一下StringTable.intern的实现:http://hg.openjdk.java.net/jdk6/jdk6/hotspot/file/d9c3790c85c1/src/share/vm/classfile/symbolTable.cpp

oop StringTable::intern(Symbol* symbol, TRAPS) {
  if (symbol == NULL) return NULL;
  ResourceMark rm(THREAD);
  int length;
  jchar* chars = symbol->as_unicode(length); // 根据symbol获取字符串的指针
  Handle string;
  oop result = intern(string, chars, length, CHECK_NULL);
  return result;
}
oop StringTable::intern(Handle string_or_null, jchar* name,
                        int len, TRAPS) {
  unsigned int hashValue = hash_string(name, len); // hash字符串数据
  int index = the_table()->hash_to_index(hashValue); // 映射到StringTable的数组下标
  oop found_string = the_table()->lookup(index, name, len, hashValue); // 查一下StringTable中是否有相应的字符串数据

  // 如果有相应的字符串数据,则直接返回
  if (found_string != NULL) return found_string;

  debug_only(StableMemoryChecker smc(name, len * sizeof(name[0])));
  assert(!Universe::heap()->is_in_reserved(name) || GC_locker::is_active(),
         "proposed name of symbol must be stable");

  Handle string;
  // try to reuse the string if possible
  if (!string_or_null.is_null() && (!JavaObjectsInPerm || string_or_null()->is_perm())) {
    // 如果StringTable找不到相应的字符串,但字符串又存在永久带里,则直接复用
    string = string_or_null;
  } else {
    // 永久带分配一块新空间,拷贝一份堆中的字符串数据
    string = java_lang_String::create_tenured_from_unicode(name, len, CHECK_NULL);
  }

  // Grab the StringTable_lock before getting the_table() because it could
  // change at safepoint.
  MutexLocker ml(StringTable_lock, THREAD);

  // 将字符串添加到StringTable,这里跟HashMap的原理差不多
  return the_table()->basic_add(index, string, name, len,
                                hashValue, CHECK_NULL);
}

 

证毕,jdk1.6,new出来的字符串调用String.intern后:

       若字符串常量池中没有相应的数据,则堆中的字符串数据会被拷贝永久代,并返回字符串常量池中的指针;

       若字符串常量池中有相应的数据,直接返回字符串常量池中的指针

 

同理也能证明,jdk1.7之后,调用intern并不会拷贝字符串数据

 

分析

那么问题来了,字符串常量池是什么样的数据结构?为什么既可以存字符串数据,又可以存字符串的逻辑地址?

 

字符串常量池是通过StringTable+内存空间实现的,StringTable仅存储指针,不存储具体的数据

 

综上:

(1)jdk1.6中所谓的字符串常量池,并不存储实际的字符串数据,字符串数据存储在永久带的某块区域(通过PSPermGen::allocate_permanent进行分配);

(2)jdk1.7同理,只是字符串数据存储在堆中

 

 

参考资料

http://java-performance.info/string-intern-in-java-6-7-8/

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值