【JDK1.8】String对象的内存分配

本文深入探讨了JDK1.8环境下String对象的内存分配与intern方法的工作原理。通过实例分析,详细解释了字符串字面量、非纯字面量在不同情况下的内存位置与引用关系,以及编译器优化对内存分配的影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

关于jvm的内存分布相信大家已经不陌生了,在此不加赘述,直接来看String在创建过程中的内存分配吧。本文所有代码均运行于JDK1.8

java version “1.8.0_172”
Java™ SE Runtime Environment (build 1.8.0_172-b11)
Java HotSpot™ 64-Bit Server VM (build 25.172-b11, mixed mode)

1. intern方法的作用

		String s1 = new StringBuffer().append("123").append("123").toString();
        String s2 = s1.intern();
        System.out.println("s1 == s2 : " + (s1 == s2));

        String s3 = new StringBuffer().append("456").append("456").toString();
        String s4 = "456456";
        System.out.println("s3 == s4 : " + (s3 == s4));
        System.out.println("s3 == s3.intern : " + (s3 == s3.intern()));

s1 == s2 : true
s3 == s4 : false
s3 == s3.intern : false

当调用intern方法时,如果池中已经包含一个与该String确定的字符串相同equals(Object)的字符串,则返回该字符串。否则,将此String对象添加到池中 (不会创建新的对象而是在池中保存指向堆中对象的引用 --JDK1.8 ,并返回此对象的引用。

这句话什么意思呢?就是说调用一个String对象的intern()方法,如果常量池中有该对象了,直接返回该字符串的引用(存在堆中就返回堆中,存在池中就返回池中),如果没有,则将该对象引用添加到池中,并返回池中的引用。

案例分析 1

分析上述代码:

  1. String s1 = new StringBuffer().append("123").append("123").toString();
    在池中创建了池:"123",在堆中创建了堆:"123123",s1指向堆:"123123"
  2. String s2 = s1.intern();
    s2 指向了堆中的堆:"123123",同时将引用置于池中
  3. 所以 s1 == s2 为 true

  1. String s3 = new StringBuffer().append("456").append("456").toString();
    在池中创建了池:"456",在堆中创建了堆:"456456",s3指向堆:"456456"
  2. String s4 = "456456";
    在池中创建了池:"456456",s4 指向了池中的池:"456456"
  3. 所以 s3 == s4 为 false
  4. System.out.println("s3 == s3.intern : " + (s3 == s3.intern()));
    s3 指向 堆:"456456" 此时,由于s4的创建,池中有了和s3内容相同的String对象
    s3.intern 指向 池:"456456" 【与 [2.]内容相对】

		String s1 = new StringBuffer().append("123").append("123").toString();
        String s2 = s1.intern();
        String s3 = "123123";
        System.out.println("s1 == s2 : " + (s1 == s2));
        System.out.println("s1 == s3 : " + (s1 == s3));
        System.out.println("s2 == s3 : " + (s2 == s3));

s1 == s2 : true
s1 == s3 : true
s2 == s3 : true

  1. 在上述代码中由于s1.intern();的执行,池中保存的是堆:"123123"的引用,
    s3在创建时发现池中有相同内容String的引用,
    所以 s1 = s2 = s3 = &堆:"123123"

2. 字符串相加

		String s5 = "789" + "789" + new String("789");
        String s6 = "789789789";
        System.out.println("s5 == s6 : " + (s5 == s6));

        String s7 = "789" + "789";
        String s8 = s7.intern();
        System.out.println("s7 == s8 : " + (s7 == s8));

		String s9 = "aaa" + new String("aaa") + "aaa";
        String s10 = "aaaaaaaaa";
        System.out.println("s9 == s10 : " + (s9 == s10));

        String s11 = "bbb" + "bbb";
        String s12 = "bbbbbb";
        System.out.println("s11 == s12 : " + (s11 == s12));

        String s13 = "ccc";
        String s14 = s13 + "ccc";
        String s15 = "cccccc";
        System.out.println("s14 == s15 : " + (s14 == s15));

s5 == s6 : false
s7 == s8 : true
s9 == s10 : false
s11 == s12 : true
s14 == s15 : false

案例分析 2

分析上述代码,

  1. s5 == s6 : false 非纯字面常量相加,最终产生的对象位于堆中
  2. s7 == s8 : true 纯字面常量相加,最终产生的对象位于池中
  3. s9 == s10 : false 非纯字面常量相加,最终产生的对象位于堆中, 与顺序无关
  4. s11 == s12 : true 纯字面常量相加,最终产生的对象位于池中,和直接书写没有区别
  5. s14 == s15 : false 非纯字面常量相加,最终产生的对象位于堆中,与初始化方式无关(无论参数原本位于池中还是堆中)

 		String s2 = new StringBuffer().append("789").append("789").toString();
        System.out.println("s2 == s2.intern : " + (s2 == s2.intern()));
        // s2 == s2.intern : true
  1. 在堆中创建的对象先调用intern会将引用置于池中(实际上经过编译器优化,是直接指向堆)
		String s1 = "789" + "789" + new String("789");
        String s2 = new StringBuffer().append("789").append("789").toString();
        System.out.println("s2 == s2.intern : " + (s2 == s2.intern()));
        // s2 == s2.intern : false
  1. 在非纯字面量相加的表达式中,连续纯字面量相加部分会被优化为单个字面量,并存于池中
        String s1 = new String("789") + "789" + "789";
        String s2 = new StringBuffer().append("789").append("789").toString();
        System.out.println("s2 == s2.intern : " + (s2 == s2.intern()));
        // s2 == s2.intern : false
  1. 以上结论显然与顺序无关(实质上非纯字面量相加过程中各部分会被优化为类似new StringBuffer().append("789").append("789789").toString();的形式)
		String s16 = "ddd" + "ddd" + "ddd";
        String s17 = new StringBuffer().append("ddd").append("ddd").toString();
        String s18 = s17.intern();
        System.out.println("s17 == s18 : " + (s17 == s18));// 没有创建中间量
		// s17 == s18 : true
        String s19 = "eee" + "eee";
        String s20 = new StringBuffer().append("eee").append("eee").toString();
        String s21 = s20.intern();
        System.out.println("s20 == s21 : " + (s20 == s21));       
		// s20 == s21 : false
  1. 连续字面常量相加不会产生中间量(池中没有),只有最终结果(置于池)

小结 –JDK1.8

  1. 只有在使用"字符串字面常量"的情况下才会自动在池中创建对象[常量];
  2. 使用new、toString等方式创建的对象均处于堆中(直接使用其内部字符序列,不必求助池中常量),不会主动进行常量池的检查
  3. 使用intern方法会根据当前池中是否有相同内容对象决定返回引用,若池中无相应对象则将堆中对象的引用置入池(实际上经过编译器优化,返回值直接指向堆),有相应对象则返回对应常量 【1.8之前的情况有所不同,未测试】
  4. 在使用纯"字符串字面常量"的连加表达式中只有最终结果会被在池中创建,中间结果不会被创建(实际上应当被优化成了单一字量)
  5. 非纯字面常量相加,各部分会被优化成类似StringBuffer().append(某字符串对象).append("某字符串对象).append(某字符串对象).toString();的形式,置于堆中,不会主动进行常量池的检查

参考文章

JDK1.8源码(三)——java.lang.String 类
java基础篇之7————String常用类及jdk1.8中intern方法
关于JDK1.7和JDK1.8的内存分配(String)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值