【JVM】String 常量池与 JVM 字符串常量池的垃圾回收

转载自http://blog.csdn.net/xsf50717,做了一点修改补充。
参考:https://www.jianshu.com/p/2dc4c3dd5376
https://blog.csdn.net/xdugucc/article/details/78193805

1、String 常量池
为了减少在JVM中创建的字符串的数量,字符串类维护了一个字符串池,每当代码创建字符串常量时,JVM会首先检查字符串常量池。如果字符串已经存在池中,就返回池中的实例引用。如果字符串不在池中,就会实例化一个字符串并放到池中。

Note:常量池在java用于保存在编译期已确定的,已编译的class文件中的一份数据。它包括了关于类,方法,接口等中的常量,也包括字符串常量,如String s = "java"这种申明方式。字符串常量池在方法区中(本文是jdk1.7前编辑的,jdk1.7之后字符串常量池移至堆中)

2、True OR False

首先得看下 几个对象???
相信很多 玩java的都做过类似 String s = new String(“abc”)这个语句创建了几个对象的题目。 这种题目主要就是为了考察对字符串对象的常量池掌握与否。其实上述的语句中是创建了2个对象:

第一个对象是"abc"字符串存储在常量池中

第二个对象在JAVA Heap中的 String 对象。这里不要混淆了s是放在栈里面的指向了Heap堆中的String对象

下面再看:
String str1 = new String(“abc”);
String str2 = “abc”;
这里是两种的形式来创建,第一种是用new()来新建对象的,它会在存放于堆中。每调用一次就会创建一个新的对象。运行时期创建
而第二种是先在栈中创建一个对String类的对象引用变量str2,然后通过符号引用去字符串常量池里找有没有"abc",如果没有,则将"abc"存放进字符串常量池,并令str2指向”abc”,如果已经有”abc” 则直接令str2指向“abc”。“abc”存于常量池在编译期间完成

还有几个经常考的面试题: 
  String s1 = new String(“s1”) ;
  String s2 = new String(“s1”) ;
  上面创建了几个String对象?
  答案:3个 ,编译期Constant Pool中创建1个,运行期heap中创建2个.(用new创建的每new一次就在堆上创建一个对象,用引号创建的如果在常量池中已有就直接指向,不用创建)

比较类里面的数值是否相等时,用equals()方法;当测试两个包装类的引用是否指向同一个对象时,用==,下面用例子说明上面的理论。
String str1 = “abc”;
String str2 = “abc”;
System.out.println(str1==str2); //true
可以看出str1和str2是指向同一个对象的。

String str1 =new String (“abc”);
String str2 =new String (“abc”);
System.out.println(str1==str2); // false
用new的方式是生成不同的对象。每一次生成一个。

下面就是用几个例子说明来说明一下
例1

    String s1 = "sss111";
	String s2 = "sss111";
	System.out.println(s1 == s2); //结果为tru

例2

  String s1 = new String("sss111");
  String s2 = "sss111";
  System.out.println(s1 == s2); //结果为false

1.首先查看字符串常量池是否有字面量”sss111“,没有,则在常量池中创建,然后因为new,故在堆内存中开辟空间,创建”sss111“,然后直接返回其引用
2.第二个式子,查看常量池中是否有字面量”sss111“,发现有,则直接返回其引用
也就是说,一个是引用的常量池中的字符串,一个引用的是堆中字符串,故false

例3

    String s0 = "111";              //pool
    String s1 = new String("111");  //heap
    final String s2 = "111";        //pool
    String s3 = "sss111";           //pool
    String s4 = "sss" + "111";      //pool
    String s5 = "sss" + s0;         //heap 
    String s6 = "sss" + s1;         //heap
    String s7 = "sss" + s2;         //pool
    String s8 = "sss" + s0;         //heap
 
    System.out.println(s3 == s4);   //true
    System.out.println(s3 == s5);   //false
    System.out.println(s3 == s6);   //false
    System.out.println(s3 == s7);   //true
    System.out.println(s5 == s6);   //false
    System.out.println(s5 == s8);   //false

结果上面分析,总结如下:

  1. 单独使用""引号创建的字符串都是常量,编译期就已经确定存储到Constant Pool中;
  2. 使用new String(“”)创建的对象会存储到heap中,是运行期新创建的;
  3. 使用只包含常量的字符串连接符如"aa" + "aa"创建的也是常量,编译期就能确定,已经确定存储到String Pool中,String pool中存有“aaaa”;但不会存有“aa”。
  4. 使用包含变量的字符串连接符如"aa" + s1创建的对象是运行期才创建的,存储在heap中;只要s1是变量,不论s1指向池中的字符串对象还是堆中的字符串对象,运行期s1 + “aa”操作实际上是编译器创建了StringBuilder对象进行了append操作后通过toString()返回了一个字符串对象存在heap上。
  5. String s2 = “aa” + s1; String s3 = “aa” + s1; 这种情况,虽然s2,s3都是指向了使用包含变量的字符串连接符如"aa" + s1创建的存在堆上的对象,并且都是s1 + “aa”。但是却指向两个不同的对象,两行代码实际上在堆上new出了两个StringBuilder对象来进行append操作。在Thinking in java一书中285页的例子也可以说明。
  6. 对于final String s2 = “111”。s2是一个用final修饰的变量,在编译期已知,在运行s2+"aa"时直接用常量“111”来代替s2。所以s2+"aa"等效于“111”+ “aa”。在编译期就已经生成的字符串对象“111aa”存放在常量池中。

拓展:

String str4 = str3.intern()

intern()方法表示如果str3的字符串在池中有,就返回池中的引用,否则在池中创建一个引用,再返回该引用,因此str4 str1 str2指向的是同一个字符串对象。

JVM字符串常量池会进行垃圾回收吗?
会,但是对方法区的回收“成绩”比较难以令人满意,Java虚拟机规范中确实说过可以不要求虚拟机在方法区实现垃圾回收,而字符串常量池在方法区中。

假设一个字符串"everor"已经进入了常量池,但是当前系统中没有一个String对象引用常量池中的"everor"常量(即String str = new String(“everor”);),也没有其他地方引用了这个字面量(即String str = “everor”;),如果这个时候发生了内存回收,而且有必要的话,这个"everor"常量就会被系统清理出常量池。

在HotSpot虚拟机中,方法区又被称为永久代,永久代这个概念在其它虚拟机(如BEA JRockit、IBM J9等)中是不存在的。但是使用永久代来实现方法区,并不是一个好主意,因为这样更容易遇到内存溢出问题。从JDK1.7开始,逐步使用元空间(MetaSpace)代替永久代的概念。元空间的内存大小取决于本地内存大小。元空间代替永久代,并不意味着字符串常量池就到了元空间,而是移到了堆中。

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值