Java虚拟机--String在虚拟机中的实现(十二)

2017-04-10 20:16:04来源:CSDN作者:qq_33301113人点击

知识点的梳理:

  • 虚拟机中,常量池专门用于存放字符串常量。在JDK1.6之前,它在永久区。JDK1.7之后,它转移到了堆中;

      

  • String对象的特点
    • String的特点:
      • 不变性;
      • 针对常量池的优化;
      • 类的final定义;
    • 不变性
      • 能力说明:String对象一旦生成,则不能再对它进行改变;
      • 能力作用:当一个对象需要被多线程共享,且访问频繁时,可以省略同步和锁等待的时间,提升性能;
        • 为啥能提高性能?因为对象不可变,对于所有线程都是只读的,多线程访问时,即使不加同步也不会产生数据的不一致,减少了系统的开销;
      • 注意:String.substring(),String.concat()这些修改操作,实际上并没有修改原来的字符串,而是产生了一个新的字符串!
      • 可以使用StringBufferStringBuilder来创建一个可以修改的字符串;
    • 针对常量池的优化
      • 能力说明:当两个String对象拥有相同的值时,它们只引用常量池中的同一个拷贝。
      • 能力作用:当同一个字符串反复出现时,可以大幅节省内存空间!
      • 示例:

String str1 = new String("abc");

String str2 = new String("abc");

System.out.println(str1==str2);//false

System.out.println(str1==str2.intern());//false

System.out.println("abc"==str2.intern());//true

String.intern()返回字符串在常量池的引用

  • 图示:
  • 类的final定义
    • 能力说明:String被final修饰,提高系统安全性;
    • 能力作用:在JDK1.5之前,使用final定义有助于虚拟机内联所有的final方法,提高系统效率。但在JDK1.5之后,这种方法效果不在明显~
  • String的内存泄漏
    • String的结构
      • JDK1.6中,java.lang.String3部分组成:代表字符数组的value,偏移量offset,长度count

        • 字符串的实际内容由三者共同决定,而非value一项;
        • 问题来了!如果字符串value数组包含100个字符,而count长度只有一个字节,那么这个String实际上只有一个字符,那剩余的99个就属于泄漏部分!直到这个字符串被回收才会被释放!
  • String常量池的位置
    • 常量池的位置
      • 虚拟机中,常量池专门用于存放字符串常量。在JDK1.6之前,它在永久区。JDK1.7之后,它转移到了堆中;
      • 示例:

public class StringInternOOM {

public static void main(String[] args) {

List<String> list = new ArrayList<>();

int i =0;

while(true){

list.add(String.valueOf(i++).intern());

}

}

}

运行参数:
-Xmx5m -XX:MaxPermSize=5m

JDK1.6抛出的错误:

JDK1.7抛出的错误:

  • 注意
    • String.intern()的返回值永远等于字符串常量,但这不代表在系统的任何时刻,相同字符串的intern()返回都会是一样的;
      • 例如:在一次intern()调用之后,该字符串在某一个时刻被回收,之后,再进行一次intern()调用,那么字面值相同的字符串重新被加入常量池,但是引用位置已经不同~~~~
      • 示例:

public class ConstantPool {

public static void main(String[] args) {

if(args.length==0){

return;

}

System.out.println(System.identityHashCode((args[0]+Integer.toString(0))));

System.out.println(System.identityHashCode((args[0]+Integer.toString(0)).intern()));

System.gc();

System.out.println(System.identityHashCode((args[0]+Integer.toString(0)).intern()));

}

}

代码说明:接收一个参数,用于构造字符串,构造的字符串都是在原有字符串后加上字符串"0".输出3次字符出的Hash值,第一次为字符串本身,第二次为常量池引用,第三次进行了常量池回收后的相同字符串的常量池引用。结果三次Hash值都是不同的。

注意:可以尝试注释掉显式GC操作,那么后两次Hash值理应是相同的。

 

微信扫一扫

第七城市微信公众平台