JVM学习笔记超重点——字符串String

Posted 九死九歌

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVM学习笔记超重点——字符串String相关的知识,希望对你有一定的参考价值。

一、String的不可变性


二、String底层的HashTable结构

  1st,看一个例子:

public class StringExr 

	String str = new String("good");
	char[] ch = new char[]'t', 'e', 's', 't';

	public void change(String str, char[] ch) 
		str = "test ok";
		ch[0] = 'b';
	

	public static void main(String[] args) 
		StringExr exr = new StringExr();
		exr.change(exr.str, exr.ch);
		System.out.println(exr.str);
		System.out.println(exr.ch);
	




  怎么样,是不是很诡异。此即String的不可变性。


三、String的内存分配

  Table设置的大则浪费空间,但存取快。设置太小省空间存取慢。


  jdk7中,StringTable为什么要调整位置?① permSize比较小,容易OOM。② 永久代GC频率较低。

四、字符串的拼接

五、intern()方法

概述:


  如何保证某字符串实例对象str指向的是字符串常量池中的数据?

    方法一:字面量赋值(String str = “Hello world!”;)

    方法二:调用intern()方法(str = str.intern();)

问题一:

  如下代码会创建几个对象?

public class Test 

	public static void main(String[] args) 
		String str = new String("Hello world!");
	


  查看main方法字节码:

 #2: <java/lang/String>
 #3: <Hello world!>
 #4: <java/lang/String.<init> : (Ljava/lang/String;)V>
 
 0 new #2			// 创建一个对象,为它在对空间中开辟一片内存,并将其引用值推向栈顶
 3 dup				// 将栈顶元素复制一份并压入栈顶
 4 ldc #3			// 将int、float或String型常量从常量从常量池中推入栈顶	
 6 invokespecial #4	// 执行方法(构造器,超类构造器或私有方法)
 9 astore_1			// 将栈顶元素保存到局部变量表
10 return			// 从当前方法返回空

  很容易能看出来创造了两个对象,常量池中一个,堆空间中一个。

问题二:

  如下代码会创建几个对象?

public class Test 

	public static void main(String[] args) 
		String str = new String("Hello") + new String("World");
	


  查看main方法字节码:

#2: <java/lang/StringBuilder>
#3: <java/lang/StringBuilder.<init> : ()V>
#4: <java/lang/String>
#5: <Hello>
#6: <java/lang/String.<init> : (Ljava/lang/String;)V>
#7: <java/lang/StringBuilder.append : (Ljava/lang/String;)Ljava/lang/StringBuilder;>
#8: <World>
#9<java/lang/StringBuilder.toString : ()Ljava/lang/String;>

 0 new #2			// 创建SB对象并入栈
 3 dup				// 复制栈顶
 4 invokespecial #3	// 执行SB的空参构造器
 7 new #4			// 创建String对象
10 dup
11 ldc #5			// 将常量池中字符串推入栈顶
13 invokespecial #6	// 执行String构造器
16 invokevirtual #7 // 执行SB的append方法
19 new #4			// 创建String对象
22 dup
23 ldc #8
25 invokespecial #6
28 invokevirtual #7 
31 invokevirtual #9 // 执行SB的toString方法
34 astore_1
35 return

  所以是六个对象:① SB ② new String(“Hello”) ③ “Hello” ④ new String(“World”) ⑤ “World” ⑥ SB的toString方法里面的new String

问题三:

  分析以下代码的运行结果及出现的原因。

public class Test 

	public static void main(String[] args) 

		String s1 = new String("Hello");
		s1.intern();
		String s2 = "Hello";
		System.out.println(s1 == s2);

		String s3 = new String("Hello") + new String("World");
		s3.intern();
		String s4 = "HelloWorld";
		System.out.println(s3 == s4);

	


  查看字节码:

#2:  <java/lang/String>
#3:  <Hello>
#4:  <java/lang/String.<init> : (Ljava/lang/String;)V>
#5:  <java/lang/String.intern : ()Ljava/lang/String;>
#6:  <java/lang/System.out : Ljava/io/PrintStream;>
#7:  <java/io/PrintStream.println : (Z)V>
#8:  <java/lang/StringBuilder>
#9:  <java/lang/StringBuilder.<init> : ()V>
#10: <java/lang/String.<init> : (Ljava/lang/String;)V>
#11: <World>
#12: <java/lang/StringBuilder.toString : ()Ljava/lang/String;>
#13: <HelloWorld>

 0 new #2
 3 dup
 4 ldc #3
 6 invokespecial #4
 9 astore_1
10 aload_1
11 invokevirtual #5
14 pop
15 ldc #3
17 astore_2
18 getstatic #6
21 aload_1
22 aload_2
23 if_acmpne 30
26 iconst_1
27 goto 31
30 iconst_0
31 invokevirtual #7
34 new #8
37 dup
38 invokespecial #9
41 new #2
44 dup
45 ldc #3
47 invokespecial #4
50 invokevirtual #10
53 new #2
56 dup
57 ldc #11
59 invokespecial #4
62 invokevirtual #10
65 invokevirtual #12
68 astore_3
69 aload_3
70 invokevirtual #5
73 pop
74 ldc #13
76 astore 4
78 getstatic #6
81 aload_3
82 aload 4
84 if_acmpne 91
87 iconst_1
88 goto 92
91 iconst_0
92 invokevirtual #7
95 return

  运行结果:在jdk6中,两个false,而在jdk7/8中,是一个false,一个true。

  第一个,由于字符串常量池中已存在"Hello",所以该函数不会在字符串常量池中存储s2,所以最后输出false。

  而第二个,首先对于jdk6而言,很好解释,s3是堆空间中,s4是永久代,区域不一样,指针也不可能一样。而对于jdk7/8,创建s3后,堆空间中出现了这个对象,再要通过intern往常量池中添加这个对象,只需要添加这个对象在堆空间中的地址就好了。所以s3指向堆空间,s4指向常量池,但常量池中又是对象在堆空间中的地址,最终还是指向堆空间。所以返回true。

问题四:

  分析以下代码的运行结果及出现的原因。

public class Test 

	public static void main(String[] args) 

		String s3 = new String("Hello") + new String("World");
		String s4 = "HelloWorld";
		s3.intern();
		System.out.println(s3 == s4);

	


  输出false,因为s4声明十已经往字符串常量池中放入这个对象了,注意,不是堆空间实例的地址,而是实实在在的一个对象。这样一来,s3指向堆空间,而s4指向的是字符串常量池。而执行intern的时候就会发现字符串常量池中已有该对象,便除了返回指向常量池的引用(返回值未赋值给任何变量),不会进行任何操作。

问题五:

public class Test 

	public static void main(String[] args) 

		String s1 = new String("Hello") + new String("World");
		String s2 = s1.intern();

		System.out.println(s1 == "HelloWorld");
		System.out.println(s2 == "HelloWorld");

	


  jdk6中false true,jdk7/8中,true true。

问题六:

public class Test 

	public static void main(String[] args) 

		String s1 = new String("Hello") + new String("World");
		s1.intern();
		String s2 = "HelloWorld";
		System.out.println(s1 == s2);

	


  jdk7/8中返回true,jdk6中返回false

总结

  如果程序中出现大量的重复字符串,那么使用itern可以节省内存空间。

六、G1垃圾收集器的String去重操作



以上是关于JVM学习笔记超重点——字符串String的主要内容,如果未能解决你的问题,请参考以下文章

java String 类

python学习笔记之字符串

Day337&338.StringTable -JVM

java中的String常量是存放在栈中还是堆中?

电子科技大学人工智能期末复习笔记:MDP与强化学习

2017/03/06学习笔记