zoukankan      html  css  js  c++  java
  • 作业10:String类

    一、基本案例

    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

    // openjdk10jdksrcshare
    ativejavalangString.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);
    }
    
    // openjdk10hotspotsrcsharevmprimsjvm.h
    /*
     * java.lang.String
     */
    JNIEXPORT jstring JNICALL
    JVM_InternString(JNIEnv *env, jstring str);
    
    // openjdk10hotspotsrcsharevmprimsjvm.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
    
    // openjdk10hotspotsrcsharevmclassfilestringTable.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);
    }
    
    // openjdk10hotspotsrcsharevmclassfilestringTable.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;
    }
    
    // openjdk10hotspotsrcsharevmclassfilejavaClasses.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) { 
        // openjdk10hotspotsrcsharevmclassfilejavaClasses.hpp中static修饰的类方法
        return java_lang_String::hash_code(value->byte_at_addr(0), length);
      } else {
        // openjdk10hotspotsrcsharevmclassfilejavaClasses.hpp中static修饰的类方法
        return java_lang_String::hash_code(value->char_at_addr(0), length);
      }
    }
    
    // openjdk10hotspotsrcsharevmclassfilejavaClasses.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;
    }
    
    // openjdk10hotspotsrcsharevmclassfilestringTable.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);
    }
    
    // openjdk10hotspotsrcsharevmclassfilecompactHashtable.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;
    }
    
    // openjdk10hotspotsrcsharevmclassfilestringTable.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;
    }
    
    // openjdk10hotspotsrcsharevmclassfilestringTable.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;
    
      // openjdk10hotspotsrcsharevmutilitieshashtable.cpp
      // StringTable继承了HashTable,()是Handle的运算符重载,返回string的对象值
      HashtableEntry<oop, mtSymbol>* entry = new_entry(hashValue, string());
      add_entry(index, entry);
      return string();
    }
    
    // openjdk10hotspotsrcsharevmutilitieshashtable.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;
    }
    
    // openjdk10hotspotsrcsharevm
    untimehandles.hpp
    class Handle VALUE_OBJ_CLASS_SPEC {
        private: 
        	oop* _handle;
    	protected:
      		oop obj() const { return _handle == NULL ? (oop)NULL : *_handle; }// ()运算符重载
    }
    
  • 相关阅读:
    常用英语口语绝佳句型100句
    Mac Keyboard Shortcuts
    Linux中.a,.la,.o,.so文件的意义和编程实现
    走近GCC 4——GCC 4新特性揭秘(转)
    python 中移去文件的只读属性
    写给金融危机下毕业生的16条忠告
    C++中如何强制inline函数(MSVC, GCC)
    #pragma hdrstop
    富人和穷人的差别(转)
    商业周刊评出08年增长最快的美国科技公司
  • 原文地址:https://www.cnblogs.com/linzhanfly/p/9494359.html
Copyright © 2011-2022 走看看