Java 深入JVM分析String StringTable

文章目录

基本特性:

1、字符串常量池Jdk1.7之前位于方法区,1.7开始位于堆

2、字符串常量池中同样的数据只存储一份(固定大小HashTable存储数据)

3、使用 -XX:StringTableSize 可设置大小,不会像HashMap一样动态扩容,值太小造型Hash冲突严重,调用String.interns时性能会大幅下降

4、Jdk1.8中默认大小60013,1009是可设置最小值

字符串拼接:

1、通过StringBuilder的append()方法拼接字符串,自始至终只会创建一个StringBuilder的对象

2、使用String的字符串拼接,每次拼接都会创建一个StringBuilder和String对象,内存占用增大,也会增加GC频率

字符串拼接优化:

1、理论上初始化StringBuilder对象时,指定大小,从而设的数组大小,可以提高效率(减少扩容、复制次数)

2、但是,通过测试,发现设定大小与不设定,耗时相差无几(毫秒)

intern方法的使用:

情况一:intern方法会从字符串常量池中查询当前字符串是否存在,若不存在,则会将当前字符串放入常量池中并把地址返回栈中引用,存在则将地址返回给栈中引用。

String s1 = "JavaEEHadoop"; //在字符串常量池中创建 "JavaEEHadoop"
String s2 = new String("JavaEEHadoop").intern(); //会将字符串常量池中 "JavaEEHadoop" 地址 返回s2
System.out.println(s1 == s2); true

情况二:如果对象在堆中已经创建了,字符串常量池中就不需要再创建新的对象了,而是直接保存堆中对象的引用,也就节省了一部分的内存空间

下述情况适用于Jdk1.7、1.8

/**
* 此代码会在字符串常量池中 创建 "JavaEE"、”Hadoop“
* 会使用StringBUilder来拼接,最后执行toString方法
* 
* 此时在堆中是存在值为 "JavaEEHadoop" 的字符串对象的
*/
String s1 = new String("JavaEE") + new String("Hadoop");

/**
* 由于s1在堆中已存在,因此为了节省空间,字符串常量池中并不会创建 "JavaEEHadoop"
* 而是保存 堆中对象的引用
*/
s1.intern();

/**
* 由于此刻字符串常量池中已存在,因此s2会指向常量池中 堆中对象的引用
*/
String s2 = "JavaEEHadoop";

System.out.println(s1 == s2); true

是否引用同一份对象?

String s1 = "JavaEE";
String s2 = "Hadoop";

String s3 = "JavaEEHadoop";
/**
* 编译器优化 为 JavaEEHadoop,s3 == s4 为true,在字符串常量池中同一份
*/
String s4 = "JavaEE" + "Hadoop";
System.out.println(s3 == s4); true

/**
* 如果拼接符号的前后出现了变量
* 1、StringBuilder.toString中 s = new StringBuilder();
* 2、s.append("JavaEE");
* 3、s.append("Hadoop");
* 4、s.toString(); -->类似于 new String(char[])
* 但跟String s = new String("JavaEEHadoop")不一样
* 由于StringBuilder.toString中new String中参数是char[]数组
* 因此并不会在字符串常量池中创建 ”JavaEEHadoop“
*/
String s1 = "JavaEE"; //字符串常量池中 "JavaEE"
String s2 = "Hadoop"; //字符串常量池中 "Hadoop"
String s3 = "JavaEEHadoop";
String s4 = s1 + "Hadoop"; 
String s5 = s1 + s2;
System.out.println(s3 == s4); false
System.out.println(s3 == s5); false

/**
* final修饰的string变量相加时,编译器会优化为 ab,不会用StringBuilder拼接
* s11 == s12 为true,在字符串常量池中同一份
* s12会被编译器优化为 "ab"
*/
final String s1 = "a";
final String s2 = "b";
String s3 = "ab";
String s4 = s1 + s2;
System.out.println(s2 == s4); true

创建了几个对象?

/**
* 创建了1个或2个
* 执行步骤,对应字节码步骤
* 
* 1、堆中开辟String对象空间  new #8  
* 2、如果 "ab" 在字符串常量池中存在,那么久不创建,如果不存在则创建
* 3、初始化String对象
*/
String s1 = new String("ab");
/**
* 字节码
*/
步骤1、new #17 
步骤1、dup
步骤2、ldc #14 
步骤3、invokespecial #18 >


/**
* 如果字符串常量池中 "a"、"b"不存在,那么会创建6个对象
* 
* 执行步骤对应字节码步骤
* 1、堆中开辟StringBuilder对象空间,初始化StringBuilder对象  
* 2、堆中开辟String对象空间
* 3、在字符串常量池中创建 "a"
* 4、初始化String对象
* 5、执行append方法
* 
* 6、堆中开辟String对象空间
* 7、在字符串常量池中创建 "b"
* 8、初始化String对象
* 9、执行append方法
* 10、执行toString方法

* StringBuilder对象toString方法执行说明,由于返回的是String对象,因此会执行toString方法
* 11、堆中开辟String对象空间
* 由于调用的是new String(char[])构造方法
* 因此并不会在字符串常量池中创建 "ab"
*/
String s1 = new String("a") + new String("b");

/**
* 字节码文件
*/
步骤1、new #8 
步骤1、dup
步骤1、invokespecial #9 >
步骤2、new #17 
步骤2、dup
步骤3、ldc #12 
步骤4、invokespecial #18 >
步骤5、invokevirtual #10 
    
步骤6、new #17 
步骤6、dup
步骤7、ldc #13 
步骤8、invokespecial #18 >
步骤9、invokevirtual #10 
步骤10、invokevirtual #11 
    
步骤11、new #80 
步骤11、dup
//我们可以看到toString方法中并没有 在字符串常量池中创建 "ab"
aload_0
getfield #234 
iconst_0
aload_0
getfield #233 
invokespecial #291 >
areturn

提示:这里对文章进行总结:

建议大家不要卷、不要卷、不要卷

深入   Java   JVM
发表评论
留言与评论(共有 0 条评论) “”
   
验证码:

相关文章

推荐文章