一、基本案例

1、new String("helloworld") 与 "helloworld"

public static void main(String[] args) throws Exception {
String s0 = new String("helloworld");
String s1 = s0.intern(); // 此时"helloworld"已经存在常量池中,现在只是通过intern方法取出而已
String s2 = "helloworld"; //
System.out.println(s0 == s1); // false
System.out.println(s2 == s1); // true
} // 学过java编译过程的都知道编译会进行热点代码的优化,如:方法内联、常量传播、空值检查消除、寄存器分配等等,热点代码一般通过热点探测得出,而HotSpotIntrinsicCandidate注解能够直接手段将某个方法直接指定为热点代码,jvm尽快优化它(非绝对优化,优化时机不确定)。 // 注释简言之:new String("helloworld") 是"helloworld"的一个复制;因为String是不可变的,除非需要显示复制"hellworld",不然使用构造器来复制字符串是不必要的。 /**
* Initializes a newly created {@code String} object so that it represents
* the same sequence of characters as the argument; in other words, the
* newly created string is a copy of the argument string. Unless an
* explicit copy of {@code original} is needed, use of this constructor is
* unnecessary since Strings are immutable.
*
* @param original
* A {@code String}
*/
@HotSpotIntrinsicCandidate
public String(String original) {
this.value = original.value;
this.coder = original.coder;
this.hash = original.hash;
}

(1)对比new String("helloworld")与"helloworld"的反编译源码

// 对应的反编译源码
public static void main(java.lang.String[]) throws java.lang.Exception;
Code:
// new 创建一个对象,并将其引用值压入栈顶
0: new #2 // class java/lang/String
// 复制栈顶数值(数值不能是long或double类型的)并将复制值压入栈顶
3: dup
// 从常量池中取出字面量(常量值)
4: ldc #3 // String helloworld
6: invokespecial #4 // Method java/lang/String."<init>":(Ljava/lang/String;)V
9: astore_1
10: aload_1
11: invokevirtual #5 // Method java/lang/String.intern:()Ljava/lang/String;
14: astore_2
15: ldc #3 // String helloworld
17: astore_3
18: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
21: aload_1
22: aload_2
23: if_acmpne 30
26: iconst_1
27: goto 31
30: iconst_0
31: invokevirtual #7 // Method java/io/PrintStream.println:(Z)V
34: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
37: aload_3
38: aload_2
39: if_acmpne 46
42: iconst_1
43: goto 47
46: iconst_0
47: invokevirtual #7 // Method java/io/PrintStream.println:(Z)V
50: return
} // String s0 = new String("helloworld") 的反编译源码
0: new #2 // class java/lang/String
3: dup
4: ldc #3 // String helloworld
6: invokespecial #4 // Method java/lang/String."<init>":(Ljava/lang/String;)V
9: astore_1 // String s2 = "helloworld" 的反编译源码
15: ldc #3 // String helloworld
17: astore_3 // 对比可知,"helloworld"不会在堆中创建对象,即不调用new指令和String的构造器方法
// 但是只要看到"helloworld",就会在'静态常量池'中生成"helloworld"字面量,后面还有个例子可以比较看看。

关于JVM指令,请参考:https://blog.csdn.net/hudashi/article/details/7062781

https://blog.csdn.net/hudashi/article/details/7062675

(2)通过Classpy查看静态常量池:

关于Classpy,请参考:https://github.com/zxh0/classpy

StringDemo.class
magic:0xCAFEBABE
...
...
constant_pool:
#01 (Methodref): java/lang/Object.<init>
#02 (Class): java/lang/String
// 符号引用
#03 (String): helloworld
tag:8
string_index:26
...
...
// 字面量
#26 (Utf8):hellowrold
tag:1
length:10
bytes:helloworld

(3)intern方法(返回字符串的引用)

// 注释简言之:intern会判断字符串常量池是否拥有该字符串对象,拥有则返回,反之添加到常量池并返回该字符串对象的引用。
// 其中所有的字面量字符串和字符数值的常量表达式(请参考java规范)都会被常量池保存起来。
// 后面再详细讲解intern方法的内部实现 /**
* 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();

java规范的下载地址:https://docs.oracle.com/javase/specs/index.html

2、new String("hello") + new String("world") 与 “helloworld”

public static void main(String[] args) throws Exception {
// new StringBuilder().append("hello").append("world").toString();
String s0 = new String("hello") + new String("world");
String s1 = s0.intern(); // 此时常量池没有helloworld,此时放入,放入的是s0的地址。
String s2 = "helloworld"; // 从常量池中取出s0的地址
System.out.println(s0 == s1); // true
System.out.println(s2 == s1); // true
} public static void main(String[] args) throws Exception {
String s2 = "helloworld";
String s0 = new String("hello") + new String("world");
String s1 = s0.intern(); // 此时常量池有helloworld,直接取出,为s2的地址。
System.out.println(s0 == s1); // false
System.out.println(s2 == s1); // true
} // 此例证明new StringBuilder().append("hello").append("world").toString()没有intern的功能
public static void main(String[] args) throws Exception {
String s0 = new String("hello") + new String("world");
String s2 = "helloworld"; // 具有intern的功能
System.out.println(s2 == s0); // false
} // StringBuilder的toString方法
@Override
@HotSpotIntrinsicCandidate
public String toString() {
// Create a copy, don't share the array
return isLatin1() ? StringLatin1.newString(value, 0, count): StringUTF16.newString(value, 0, count);
} // StringLatin1.newString方法 ==> String的重载构造器
public static String newString(byte[] val, int index, int len) {
return new String(Arrays.copyOfRange(val, index, index + len),LATIN1);
}

(1)对应的反编译源码

public static void main(java.lang.String[]) throws java.lang.Exception;
Code:
0: new #2 // class java/lang/StringBuilder
3: dup
4: invokespecial #3 // Method java/lang/StringBuilder."<init>":()V
7: new #4 // class java/lang/String
10: dup
11: ldc #5 // String hello
13: invokespecial #6 // Method java/lang/String."<init>":(Ljava/lang/String;)V
16: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: new #4 // class java/lang/String
22: dup
23: ldc #8 // String world
25: invokespecial #6 // Method java/lang/String."<init>":(Ljava/lang/String;)V
28: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
31: invokevirtual #9 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
34: astore_1
35: aload_1
36: invokevirtual #10 // Method java/lang/String.intern:()Ljava/lang/String;
39: astore_2
40: ldc #11 // String helloworld
42: astore_3
43: getstatic #12 // Field java/lang/System.out:Ljava/io/PrintStream;
46: aload_1
47: aload_2
48: if_acmpne 55
51: iconst_1
52: goto 56
55: iconst_0
56: invokevirtual #13 // Method java/io/PrintStream.println:(Z)V
59: getstatic #12 // Field java/lang/System.out:Ljava/io/PrintStream;
62: aload_3
63: aload_2
64: if_acmpne 71
67: iconst_1
68: goto 72
71: iconst_0
72: invokevirtual #13 // Method java/io/PrintStream.println:(Z)V
75: return
}

(2)通过Classpy查看静态常量池:

  • 检查静态常量池可知,只有"hello"和"world",但是上述结果为true,代表执行intern时,运行常量池中放入了s0指向的地址。

(3)通过HSDB查看一下main线程的状态

  • 代码通过jdb打断点停止在 String s1 = s0.intern();这一行。

  • cmd中,输入java -classpath "%JAVA_HOME%/lib/sa-jdi.jar" sun.jvm.hotspot.HSDB

  • 点击右上角File -> Attach Hotspot process

  • 点击Java Threads的main线程,点击Stack Memory...(左上角第二个)

  • 查看Stack Memory for main,看到PSYoungGen java/langString 的地址:0x00000000d61515b8

  • 点击Windows -> Console,输入inspect 0x00000000d61515b8,返回了instance of [C @ 0x00000000d61515d0 @ 0x00000000d61515d0 (size = 40)

  • Console Line中,继续输入inspect 0x00000000d61515d0 ,得到"helloworld",代表new StringBuilder().toString()不会再常量池中放入字符串的引用。

  • 断开HSDB的连接,代码通过jdb继续运行至System.out.println(s0 == s1);该行(HSDB连接时线程将阻塞,gdb无法直接调试,需要断开调试,再连接查看)
  • 连接HSDB到虚拟机,明显可以看出main线程中的3个本地变量指向同一个字符串,对应s0、s1、s2.

HSDB,请参考:https://blog.csdn.net/kisimple/article/details/45128525

GBD,请参考:https://www.cnblogs.com/rocedu/p/6371262.html

二、String.intern()源码

由于intern方法是native方法,采用了JNI技术。

关于JNI技术,请参考:https://www.cnblogs.com/DengGao/p/jni.html

为了理解简单,下面源码省略了加锁、内存管理和重哈希的代码,感兴趣可以下载HotSpot的源码进行研读。

通过源码可以知道,String的常量池其实就是C++版本的HashMap而已。

下载源码,请参考:https://www.cnblogs.com/linzhanfly/p/9474173.html

// \openjdk10\jdk\src\share\native\java\lang\String.c
// 第二个参数为返回值
JNIEXPORT jobject JNICALL
// jni命名规范(声明为native自动生成):java.lang.String:intern => Java_java_lang_String_intern(Java前缀 + 包名 + 方法名,分隔符号采用_)
Java_java_lang_String_intern(JNIEnv *env, jobject this){
//(1)JVM_InternString调用
return JVM_InternString(env, this);
} // \openjdk10\hotspot\src\share\vm\prims\jvm.h
/*
* java.lang.String
*/
JNIEXPORT jstring JNICALL
JVM_InternString(JNIEnv *env, jstring str); // \openjdk10\hotspot\src\share\vm\prims\jvm.cpp
// String support ///////////////////////////////////////////////////////////////////////////
// (2)JVM_InternString的实现
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);
// (3)StringTable::intern调用
oop result = StringTable::intern(string, CHECK_NULL);
return (jstring) JNIHandles::make_local(env, result);
JVM_END // \openjdk10\hotspot\src\share\vm\classfile\stringTable.cpp
// (4)StringTable::intern的实现 StringTable是HashTable的子类
oop StringTable::intern(oop string, TRAPS){
if (string == NULL) return NULL;
int length;
Handle h_string (THREAD, string); // 创建Handle
jchar* chars = java_lang_String::as_unicode_string(string, length, CHECK_NULL);
// (5)StringTable::intern的重载方法
return intern(h_string, chars, length, CHECK_NULL);
} // \openjdk10\hotspot\src\share\vm\classfile\stringTable.cpp
oop StringTable::intern(Handle string_or_null, jchar* name,int len, TRAPS) {
// shared table always uses java_lang_String::hash_code
// 个人理解: java_lang_String属于工具类,提供一些操作string的方法
unsigned int hashValue = java_lang_String::hash_code(name, len);
// (6)查询共享数组
oop found_string = lookup_shared(name, len, hashValue);
if (found_string != NULL) return found_string; // the_table()返回StringTable的引用
int index = the_table() -> hash_to_index(hashValue);// 其实就是hashValue % _table_size
found_string = the_table() -> lookup_in_main_table(index, name, len, hashValue);
if (found_string != NULL) return found_string; Handle string;
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 = the_table()->basic_add(index,string,name,len,hashValue,CHECK_NULL);
return added_or_found;
} // \openjdk10\hotspot\src\share\vm\classfile\javaClasses.cpp
// hash_code的实现,与jdk源码String类的HashCode()方法类似
unsigned int java_lang_String::hash_code(oop java_string) {
int length = java_lang_String::length(java_string);
if (length == 0) return 0; typeArrayOop value = java_lang_String::value(java_string);
bool is_latin1 = java_lang_String::is_latin1(java_string); if (is_latin1) {
// \openjdk10\hotspot\src\share\vm\classfile\javaClasses.hpp中static修饰的类方法
return java_lang_String::hash_code(value->byte_at_addr(0), length);
} else {
// \openjdk10\hotspot\src\share\vm\classfile\javaClasses.hpp中static修饰的类方法
return java_lang_String::hash_code(value->char_at_addr(0), length);
}
} // \openjdk10\hotspot\src\share\vm\classfile\javaClasses.hpp
static unsigned int hash_code(const jbyte* s, int len) {
unsigned int h = 0;
while (len-- > 0) {
h = 31*h + (((unsigned int) *s) & 0xFF);
s++;
}
return h;
} // package java.lang.StringLatin1类中的hashCode与Openjdk中保持一致
public static int hashCode(byte[] value) {
int h = 0;
for (byte v : value) {
h = 31 * h + (v & 0xff);
}
return h;
} // \openjdk10\hotspot\src\share\vm\classfile\stringTable.cpp
oop StringTable::lookup_shared(jchar* name, int len, unsigned int hash) {
//(7)共享数组是一个HashTable的子类, CompactHashtable<oop, char> StringTable::_shared_table;
return _shared_table.lookup((const char*)name, hash, len);
} // \openjdk10\hotspot\src\share\vm\classfile\compactHashtable.inline.hpp
template <class T, class N>
inline T CompactHashtable<T,N>::lookup(const N* name, unsigned int hash, int len) {
if (_entry_count > 0) {//
int index = hash % _bucket_count;// _bucket_count为_buckets数组大小
u4 bucket_info = _buckets[index];// bucket_info为32位,高2位代表类型,低30为代表偏移量
u4 bucket_offset = BUCKET_OFFSET(bucket_info);// 取出低30位
int bucket_type = BUCKET_TYPE(bucket_info);// 取出高2位
u4* entry = _entries + bucket_offset;// 根据偏移量取出entries数组中值 if (bucket_type == VALUE_ONLY_BUCKET_TYPE) {
// 只存值的entry,包含一个偏移量
T res = decode_entry(this, entry[0], name, len);// 获取存放的值,代码就不贴了
if (res != NULL) return res;
} else {
// This is a regular bucket, which has more than one
// entries. Each entry is a pair of entry (hash, offset).
// Seek until the end of the bucket. // 常规bucket,索引0放着hash值,索引1放着偏移量
u4* entry_max = _entries + BUCKET_OFFSET(_buckets[index + 1]);// 获取下一个_buckets的偏移量作为寻找entry的最大值
while (entry < entry_max) {
if ((unsigned int)(entry[0]) == hash) {
T res = decode_entry(this, entry[1], name, len);
if (res != NULL) return res;
}
entry += 2;
}
}
}
return NULL;
} // \openjdk10\hotspot\src\share\vm\classfile\stringTable.cpp
oop StringTable::lookup_in_main_table(int index, jchar* name,int len, unsigned int hash) {
// bucket方法位于hashtable.inline.hpp中,与java的HashMap类似,取出HashtableEntry,类比Map.Entry.单向链表形式。
// hash碰撞导致index相同,存放形式为链表。所以需要取出来对比hash值和内部值是否相等。
// bucket(index) ==> _buckets[i].get_entry();
for (HashtableEntry<oop, mtSymbol>* l = bucket(index); l != NULL; l = l->next()) {
// hash方法 ==> unsigned int hash() const { return _hash; }
if (l->hash() == hash) {
// literal方法取出oop,即String字面量 ==> T literal() const { return _literal;}
if (java_lang_String::equals(l->literal(), name, len)) return l->literal();
}
}
return NULL;
} // \openjdk10\hotspot\src\share\vm\classfile\stringTable.cpp
oop StringTable::basic_add(int index_arg, Handle string, jchar* name,int len, unsigned int hashValue_arg, TRAPS) {
unsigned int hashValue = hashValue_arg;
int index = index_arg; oop test = lookup_in_main_table(index, name, len, hashValue);
if (test != NULL) return test; // \openjdk10\hotspot\src\share\vm\utilities\hashtable.cpp
// StringTable继承了HashTable,()是Handle的运算符重载,返回string的对象值
HashtableEntry<oop, mtSymbol>* entry = new_entry(hashValue, string());
add_entry(index, entry);
return string();
} // \openjdk10\hotspot\src\share\vm\utilities\hashtable.inline.cpp
template <MEMFLAGS F> inline void BasicHashtable<F>::add_entry(int index, BasicHashtableEntry<F>* entry) {
entry->set_next(bucket(index));
_buckets[index].set_entry(entry);
++_number_of_entries;
} // \openjdk10\hotspot\src\share\vm\runtime\handles.hpp
class Handle VALUE_OBJ_CLASS_SPEC {
private:
oop* _handle;
protected:
oop obj() const { return _handle == NULL ? (oop)NULL : *_handle; }// ()运算符重载
}

作业10:String类的更多相关文章

  1. 全面深入介绍C++字符串:string类

    http://blog.csdn.net/liuliming3000/article/details/1809385 1 从C到C++ string类 2 string类的构造函数 3 string类 ...

  2. Java第二次作业--数组和String类

    Deadline: 2017-3-28 23:00 一.学习要点 认真看书并查阅相关资料,掌握以下内容: 掌握基本数据类型和引用数据类型的区别 理解对象的生成与引用的关系 掌握构造方法的重载 掌握St ...

  3. Java第二次作业——数组和String类

    Java第二次作业--数组和String类 学习总结 1.学习使用Eclipse关联jdk源代码,查看String类的equals()方法,截图,并学习其实现方法.举例说明equals方法和==的区别 ...

  4. 05-Java中的String类

    程序设计思路: 首先目标是使输入的字符串加上某个数变成另一个字符串,从而相当于对字符串进行加密. 第一步输入一个字符串String类型: 第二步把这个字符串转变成字符数组: 第三步让这个数组的每一个字 ...

  5. Java学习笔记 02 String类、StringBuilder类、字符串格式化和正则表达式

    一.String类一般字符串 声明字符串 >>String str 创建字符串 >>String(char a[])方法用于将一个字符数组创建为String对象 >> ...

  6. Java api 入门教程 之 JAVA的String 类

    1.String对象的初始化 由于String对象特别常用,所以在对String对象进行初始化时,Java提供了一种简化的特殊语法,格式如下: String s = “abc”; s = “Java语 ...

  7. String 类相关知识

    1.常用方法 1)判断字符串是否为空 public boolean isEmpty()2)获取字符串长度 public int length()3)截取子子串 public String substr ...

  8. 关于JAVA的String类的一些方法

    一.得到字符串对象的有关信息 1.通过调用length()方法得到String的长度. String str=”This is a String”; int len =str.length(); 2. ...

  9. Java String类的常用方法

    String(byte[ ] bytes):通过byte数组构造字符串对象. String(char[ ] value):通过char数组构造字符串对象. String(Sting original) ...

随机推荐

  1. 20190710记录:去掉中转图,直接以1280*1024进行反坐标计算,填补pbFinal。

    1.记录:去掉中转图,直接以1280*1024进行反坐标计算.pbFinal=1280*1024. // Imagejoint.cpp : 定义控制台应用程序的入口点. // #include &qu ...

  2. CIEDE2000色差公式相关

    色差公式发展的三个重要的阶段:1976年以前(CIELAB和CIELUV的采用).1976年到2001年(CIEDE2000色差公式的推荐).2001年以后. 国际照明委员会1998年成立了技术委员会 ...

  3. SQL-W3School-高级:SQL INNER JOIN 关键字

    ylbtech-SQL-W3School-高级:SQL INNER JOIN 关键字 1.返回顶部 1. SQL INNER JOIN 关键字 在表中存在至少一个匹配时,INNER JOIN 关键字返 ...

  4. go一个简单的爬虫(豆瓣)

    最近在学习go语言爬虫,写了个小demo package main import ( "fmt" "io/ioutil" "net/http" ...

  5. JVM的线程

    我们使用java命令来运行一个程序,那么就需要启动JVM , 而jvm的启动就相当于启动了一个进程 , 而这个进程在启动的时候会自动启动一个线程,由这个线程去调用main方法,而这个线程就是主线程 ; ...

  6. numpy中flatten学习笔记

    ndarray.flatten() 用法 用于返回一个折叠成一维的数组.该函数只能适用于numpy对象,即array或者mat,普通的list列表是不行的. 例子 # coding=utf-8 fro ...

  7. Queue class

    #pragma once#include <iostream>#include <iomanip> using namespace std; class Queue{ stru ...

  8. Java NIO学习笔记八 Pipe

    Java NIO Pipe Java NIO管道是两个线程之间的单向数据连接.Pipe 具有源信道和接受通道.您将数据写入sink通道.然后可以从源通道读取该数据. 这是一个原理的Pipe流程图: J ...

  9. 比特币 难度值(difficulty)

    难度(Difficulty) 难度是对挖矿困难程度的度量,即指:计算符合给定目标的一个HASH值的困难程度.比特币网络有一个全局的区块难度,有效的区域必须有一个HASH值,该HASH值必须小于给定的目 ...

  10. Spring Security(20)——整合Cas

    整合Cas 目录 1.1           配置登录认证 1.1.1     配置AuthenticationEntryPoint 1.1.2     配置CasAuthenticationFilt ...