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的主要内容,如果未能解决你的问题,请参考以下文章