在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. 【BZOJ 1211】 [HNOI2004]树的计数

    [链接] 我是链接,点我呀:) [题意] 在这里输入题意 [题解] prufer数列的应用 http://www.cnblogs.com/AWCXV/p/7626625.html 这一题没有节点的度数 ...

  2. 3.is null和is not null

    3.WHERE中使用is null和is not null   //查询工资是null空值的人   select * from person where salary is null;   //查询工 ...

  3. 洛谷 1052 dp 状态压缩

    洛谷1052 dp 状态压缩 传送门 (https://www.luogu.org/problem/show?pid=1052#sub) 做完这道题之后,感觉涨了好多见识,以前做的好多状压题目都是将一 ...

  4. HDU——T 1498 50 years, 50 colors

    http://acm.hdu.edu.cn/showproblem.php?pid=1498 Time Limit: 2000/1000 MS (Java/Others)    Memory Limi ...

  5. sql学习笔记(18)-----------数据库创建过程

    手动创建数据库的步骤:   第一步:决定数据库实例的SID 数据库实例的SID用来将当前实例和以后可能创建的实例进行区分 % setenv ORACLE_SID mynewdb     第二步:建立数 ...

  6. HDU 4311 Contest 2

    求的是曼哈顿距离.可以把X,Y的距离分开来求.其中,求X.Y的距离可以通过排序后递推的方式求出值的. #include <iostream> #include <algorithm& ...

  7. spark源代码action系列-foreach与foreachPartition

    RDD.foreachPartition/foreach的操作 在这个action的操作中: 这两个action主要用于对每一个partition中的iterator时行迭代的处理.通过用户传入的fu ...

  8. NOIP2012 T3开车旅行 set+倍增

    70分做法: 先预处理出所有点的最近和次近(O(n^2)一遍就OK) 然后暴力求出每个解(O(nm)) //By SiriusRen #include <cstdio> #include ...

  9. zookeeper的选举机制

    1)半数机制:集群中半数以上机器存活,集群可用.所以zookeeper适合装在奇数台机器上. 2)Zookeeper虽然在配置文件中并没有指定master和slave.但是,zookeeper工作时, ...

  10. 模仿百度首页“元宵节汤圆”动图,并实现360°不停旋转(CSS3的animation动画效果)

    模仿百度首页“元宵节汤圆”动图,并实现360°不停旋转(CSS3的animation动画效果) 效果图: 切图地址: https://ss1.bdstatic.com/5eN1bjq8AAUYm2zg ...