在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这个引用。

Java (JDK7)中的String常量和String.intern的实现的更多相关文章

  1. 【mybatis】mybatis使用java实体中定义的常量,或静态方法

    mybatis使用java实体中定义的常量 示例代码: <select id="findDealerInfo" parameterType="com.pisen.c ...

  2. String常量池和intern方法

    String s1 = "Hello"; String s2 = "Hello"; String s3 = "Hel" + "lo ...

  3. java源码学习(一)String

    String表示字符串,Java中所有字符串的字面值都是String类的实例,例如"ABC".字符串是常量,在定义之后不能被改变,字符串缓冲区支持可变的字符串.因为 String ...

  4. Java堆、栈和常量池以及相关String的详细讲解(经典中的经典) (转)

    原文链接 : http://www.cnblogs.com/xiohao/p/4296088.html 一:在JAVA中,有六个不同的地方可以存储数据: 1. 寄存器(register). 这是最快的 ...

  5. java中String常量的存储原理

    相关题目(运行结果在代码注释后面) 1. package StringTest; public class test1 { public static void main(String[] args) ...

  6. 结合字符串常量池/String.intern()/String Table来谈一下你对java中String的理解

    1.字符串常量池 每创建一个字符串常量,JVM会首先检查字符串常量池,如果字符串已经在常量池中存在,那么就返回常量池中的实例引用.如果字符串不在池中,就会实例化一个字符串放到字符串池中.常量池提高了J ...

  7. Java堆、栈和常量池以及相关String的详细讲解

    一:在JAVA中,有六个不同的地方可以存储数据: 1. 寄存器(register). 这是最快的存储区,因为它位于不同于其他存储区的地方——处理器内部.但是寄存器的数量极其有限,所以寄存器由编译器根据 ...

  8. Java常量字符串String理解

    Java常量字符串String理解 以前关于String的理解仅限于三点:1.String 是final类,不可继承2.String 类比较字符串相等时时不能用“ == ”,只能用  "eq ...

  9. java基础知识回顾之---java String final类 容易混淆的java String常量池内存分析

    /** *   栈(Stack) :存放基本类型的变量数据和对象的引用,但对象本身不存放在栈中,而是存放在堆(new 出来的对象)或者常量池中(字符串常量对象存放  在常量池中). 堆(heap):存 ...

随机推荐

  1. 什么是PL/SQL,有什么用

    1.什么是PL/SQL,有什么用  Procedure Language+SQL  PL/SQL是Oracle数据库特有的编程语言.  PL/SQL程序是以SQL为基础,引入了  编程语言特点,例如变 ...

  2. Android程序全然退出的三种方法

    1. Dalvik VM的本地方法 android.os.Process.killProcess(android.os.Process.myPid())    //获取PID,眼下获取自己的也仅仅有该 ...

  3. Authentication in asp.net

    https://docs.microsoft.com/en-us/aspnet/web-forms/overview/older-versions-security/introduction/an-o ...

  4. hdoj--4501--小明系列故事——买年货(三维背包)

    小明系列故事--买年货 Time Limit: 5000/2000 MS (Java/Others)    Memory Limit: 65535/32768 K (Java/Others) Tota ...

  5. Defining and using constants from PySide in QML

    Defining and using constants from PySide in QML This PySide tutorial shows you how to define constan ...

  6. JQuery中的find、filter和each方法学习

    find() 概述 搜索所有与指定表达式匹配的元素.这个函数是找出正在处理的元素的后代元素的好方法. 所有搜索都依靠jQuery表达式来完成.这个表达式可以使用CSS1-3的选择器语法来写. 参数 e ...

  7. What's new in Safari 11.0

    导语: Safari 11.0 的亮点 网络会议.使用WebRTC标准实现对等会议. 开发调试工具增强.使用用于测量代码和网络性能的新工具来测试代码. WebAssembly.当使用新的WebAsse ...

  8. Windows phone解决GB2312编码问题

    环境win8+vse for Windows phone 在网站http://encoding4silverlight.codeplex.com/上点击下载 之后有三个文件big5.bin,DBCSE ...

  9. ing在写作中到底怎么用

    改关静留的作业中我还得改英文摘要.于是有个地方用了ing.顺便查了一下. http://www.yygrammar.com/Article/201408/3677.html -ing分词用作状语时有九 ...

  10. 【原创】Apache服务器500错误失去响应的问题解决

    某生产网站部署在Apache上,使用tomcat集群,偶尔网站失去响应,查看首页发现无法打开,页面假死,出现过多次,查看apache的日志,发现经常出现以下提示: [Fri Dec :: ] [war ...