JVM-深入学习字符串常量池

首先声明,在JDK1.7的时候,字符串常量池已经从方法区迁移到了堆内存,JDK1.8的时候方法区改朝换代为元空间,同时也不在占用JVM内存,而是使用本地内存

  • 为什么多设计一个常量池,不能像其它对象一样乖乖待在堆中吗?鄙人大胆猜测:

    • 对象的分配需要时间和空间的开销,一般在程序中。字符串使用的频段还是很高的,另一方面,常用的数据库密码、用户名啥的都是使用字符串保存的,如果不将其放入字符串常量池的话,使其频繁创建销毁的话,性能和安全性都是划不来的,所以这就是为什么会有字符串常量池和String为什么是final修饰的原因。
  • 上代码,下面这种字面量声明的方式是我们最常用的方式。这样声明会直接将字符串”晓果冻“放入字符串常量池
    image-20210513171013681

    image-20210513171819483

  • 再声明一个对象s2,因为“晓果冻”已经在字符串常量池中存在了,所以s2直接指向字符串常量池中的地址

    String s2 = "晓果冻";
    

    image-20210513172420686

  • new String("")呢,分2种情况

    1. new的字符串已经在常量池存在,那么堆就会指向字符串常量池中的地址
      image-20210513172846542

      image-20210513173857284

      ​ 为什么会是false呢,因为s1引用的是字符串常量池中的地址,s2引用的是堆内地址,但堆内对象是引用了字符串地址的,如果 s2 = s2.intern();
      image-20210513174134182

  1. 第二种情况:
    • 其实这里也不能算第二种情况,就是new好了再调用上面的**intern()**方法,又是另一种情形;
      image-20210513175201334

    • intern():不管使用什么方式定义一个字符串,都会首先在常量池中查找是否有相应的字符串存在,如果有,直接返回引用,否则,在常量池中生成相应的字符串并返回引用;

    • 下面上代码

      package com.company;
      
      public class Main {
      
          public static void main(String[] args) {
      	// write your code here
              String s1 = "晓果冻";
              String s2 = "晓果冻";
              String s3 = "晓" + "果冻";
              String s4 = new String("晓");
              String s5 = new String("果冻");
              String s6 = s4 + "果冻";
              String s7 = s4 + s5;
              String s8 = s6.intern();
              String s9 = s7.intern();
              System.out.println(s1 == s2);
              System.out.println(s1 == s3);
              System.out.println(s1 == s6);
              System.out.println(s6 == s7);
              System.out.println(s6 == s8);
              System.out.println(s8 == s9);
              System.out.printf(s7);
          }
      }
      

      image-20210513212842213
      image-20210513215748011

+号操作是有内部优化处理操作的,其实也是又new了一个新的字符串对象,所以上图字符串常量池中的"晓"和"果冻"字符串没有被引用。因为这种+操作这种图我不知道该怎么画,所以只能指向最终的堆中地址。

  • s6.intern()如果不把返回值赋值给s6,那么栈内存中的对象s6还是引用堆中的地址。
    只有s6=s6.intern();//这样栈内存中的对象s6才会引用常量池中的地址,故s1==s6
    
  • String s10 = new String("晓果冻");
    

    /如果字符串常量池中没有"晓果冻"字符串的话,那么直接在堆中创建该字符串,并不会复制一份到字符串常量池的,大多数人都会以为会复制一份到字符串常量池,其实不然。
    只有当s10.intern();在常量池新增了一个对象,但是并没有将字符串复制一份到常量池,而是直接指向了之前已经存在于堆中的字符串对象。因为在 JDK 1.7之后,字符串常量池不一定就是存字符串对象的,还有可能存储的是一个指向堆中地址的引用

常量池

Q.E.D.


一个热爱生活的95后精神小伙