JVM中的字符串常量池

JVM中的字符串常量池

串池在不同版本JVM中的内存分布

在jdk1.6中,字符串常量池和静态变量都位于方法区(HotSpot中称为永久代)中;在jdk7中,字符串常量池和静态变量转移到堆空间中;在jdk8中,新增元空间(MetaSpace,堆共享内存但不相连),去掉了方法区,方法区中的域信息、类信息、方法信息、JIT代码缓存、运行时常量池等移动至元空间。综上所诉,串池在jdk1.6中位于方法区,在jdk7和Jdk8中位于堆空间。

intern()方法

注意:判断串池中是否存在String,是利用equals方法。例如String name = new String("Pioneer") + new String("4");为了判断串池中是否存在name,底层相当于执行了if ("Pioneer4".equals(name))。另外,这里没有写String name = new String("Pioneer4");是因为编译器发现参数中出现“Pioneer4”后,后续就会将“Pioneer4”加入串池中。

方法执行效果

  1. jdk6
jdk6字符串常量池
  • 如果串池中不存在该String,则将String复制到位于方法区的串池中,返回串池中的String常量的引用。
  • 如果出串池中存在该String,则直接返回串池中的String常量的引用。
  1. jdk7、8、9
jdk7字符串常量池
  • 如果串池中不存在该String,则将指向该String对象的引用复制到串池中,并返回串池中保存的引用。
  • 如果串池中存在该String,则直接返回串池中的String常量的应用或者String对象的引用。

代码示例

  1. jdk6中,串池中不存在“xxx”和存在“xxx”的情况
public class StringPoolTest {
    public static void main(String[] args) {
        // 串池中不存在"jdk6"
        String jdkNew = new StringBuilder("jdk").append("6").toString();
        System.out.println(jdkNew == jdkNew.intern());
        System.out.println(System.identityHashCode(jdkNew));
        System.out.println(System.identityHashCode(jdkNew.intern()));

        // 串池中存在”Gosling“
        String name = "Gosling";
        String nameNew = new StringBuilder("Gos").append("ling").toString();
        System.out.println(System.identityHashCode(nameNew));
        System.out.println(System.identityHashCode(nameNew.intern()));
        System.out.println(nameNew == nameNew.intern());
    }
}

输出:
false
1507737389
235262323
536468534
1290300832
false
  1. jdk7中,串池中不存在”xxx“或String(“xxx”)引用,以及存在”xxx“或String(“xxx”)引用的情况
public class StringPoolTest {
    public static void main(String[] args) {
        // 串池中不存在"jdk7"
        System.out.println("---1---");
        String jdkNew = new StringBuilder("jdk").append("7").toString();
        System.out.println(jdkNew == jdkNew.intern());
        System.out.println(System.identityHashCode(jdkNew));
        System.out.println(System.identityHashCode(jdkNew.intern()));

        // 串池中存在"Gosling"
        System.out.println("---2---");
        String name = "Gosling";
        String nameNew = new StringBuilder("Gos").append("ling").toString();
        System.out.println(System.identityHashCode(nameNew));
        System.out.println(System.identityHashCode(nameNew.intern()));
        System.out.println(nameNew == nameNew.intern());
        System.out.println(name == nameNew.intern());

        // 串池中存在String("hobby")的引用
        System.out.println("---3---");
        String hobby = new StringBuilder("ping").append("pong").toString();
        hobby.intern();
        String hobbyNew = new String("pingpong");
        System.out.println(System.identityHashCode(hobbyNew));
        System.out.println(System.identityHashCode(hobbyNew.intern()));
        System.out.println(hobbyNew == hobbyNew.intern());
        System.out.println(hobby == hobbyNew.intern());
    }
}

输出:
---1---
true
1735600054
1735600054
---2---
21685669
2133927002
false
true
---3---
1836019240
325040804
false
true
  1. 期望在串池中创建数量大致相同的字符串,由于jdk1.6的串池在方法区中,所以会出现OOM;而在jdk7中,可以成功执行

运行参数:-XX:PermSize=10m -XX:MaxPermSize=15m

public class StringPoolSizeTest {
    public static void main(String args[]) {
        List<String> list = new ArrayList();
        for (int i=0; i<Integer.MAX_VALUE; i++) {
            list.add(String.valueOf(i).intern());
        }
    }
}

jdk1.6中方法区内存溢出:
Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
	at java.lang.String.intern(Native Method)
	at name.electricalqzhang.concurrencylearn.jvm.base.StringPoolSizeTest.main(StringPoolSizeTest.java:10)

jdk7中正常执行
  1. jdk7中使用intern()和不使用intern()的内存占用情况:
public class StringPoolGCTest {
    public static void main(String args[]) {
        Random random = new Random();
        List<String> list = new ArrayList();
        for (int i=0; i<1000000; i++) {
        	// 测试执行intern()和不执行intern()的内存占用情况
            list.add(String.valueOf(random.nextInt(10)));
//            list.add(String.valueOf(random.nextInt(10)).intern());
        }

        try {
            Thread.sleep(1000*1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
  • 使用intern(),创建的多数String对象都会被GC回收,占用内存小
image-20210129024650466 image-20210129023939417
  • 不使用intern(),导致内存占用大,从JProfiler监控图中可以看到,存活的String实例的数量约等于创建的1000000:

    image-20210129024357504
image-20210129024510307
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
### 回答1: 字符串常量池JVM属于方法区(也称为永久代)内存分区。方法区是各个线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。字符串常量池是方法区的一部分,用于存储字符串常量。在JDK 8之后,方法区被取消了,取而代之的是元空间(MetaSpace),但字符串常量池仍然存放在元空间。 ### 回答2: 字符串常量池JVM的方法区(也称为非区)JVM将内存分为几个不同的区域,包括区、方法区、虚拟机栈等。而字符串常量池是方法区的一部分,用于存储在程序直接使用的字符串常量。 在Java字符串常量池是一种特殊的内存存储区域,用于存储字符串常量,它的作用是提高字符串的重用性和效率。当我们使用双引号声明一个字符串时,JVM会首先在字符串常量池查找是否存在相同内容的字符串,如果存在则直接返回引用,如果不存在则创建一个新的字符串并放入字符串常量池。这种机制可以减少内存占用,提高程序的执行效率。 由于字符串常量池位于方法区,它是与其他线程共享的,在程序运行过程,多个线程可以同时访问字符串常量池。而且,字符串常量池的位置是在程序的执行过程被动态调整的,当字符串没有被引用时,JVM会自动回收字符串常量池空间。 总结来说,字符串常量池JVM的方法区的一部分,用于存储程序直接使用的字符串常量,并提高字符串的重用性和效率。 ### 回答3: 字符串常量池JVM的方法区里。方法区是JVM的一个内存分区,用于存储类信息、常量、静态变量、即时编译器编译后的代码等。而字符串常量池就是方法区的一部分,用于存储字符串常量。 在Java,当我们使用字符串字面量(如"hello")时,编译器会将其放入字符串常量池。当程序执行时,如果再次使用相同的字符串字面量,JVM会直接从字符串常量池取出已存在的字符串对象,而不会创建新的对象,这样可以节省内存空间。 由于字符串Java使用非常频繁,所以将字符串常量池放在方法区,可以提高字符串的重用率。此外,字符串常量池的位置在方法区也有利于GC(垃圾回收),因为当某个字符串不再被引用时,GC可以更方便地回收该字符串常量。 需要注意的是,从Java 7开始,字符串常量池被移出了PermGen空间(方法区的前身),并放置在,这是因为字符串常量池字符串对象是可以被垃圾回收的,而且过多的字符串常量可能导致PermGen空间溢出的问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值