JVM---运行时数据区-堆-字符串常量池

 

/**
     *      <java.lang.String>
     *
     *          基本特性
     *              字符串,使用一对 "" 表示;
     *
     *              ***不可变
     *                  1、堆中字符串常量池 不会存储相同内容的字符串
     *                  eg:
     *                  private static void testStringConstantPool1() {
     *
     *                      String s = "a";
     *                      String ss = "a";
     *
     *                      System.out.println(s == ss); // true
     *                      System.out.println(s); // a
     *                      System.out.println(ss); // a
     *
     *                  }
     *
     *                  2、当变量指向新的字符串时,堆中字符串常量池 旧的字符串不会被移除
     *                  eg:
     *                  private static void testStringConstantPool2() {
     *                      String s = "a";
     *                      String ss = "a";
     *                      ss = "hello";
     *
     *                      System.out.println(s == ss); // false
     *                      System.out.println(s); // a
     *                      System.out.println(ss); // hello
     *                  }
     *
     *                  3、当对旧有 堆中字符串常量池 的字符串进行操作时,旧有字符串不会被移除
     *                  eg:
     *                  private static void testStringConstantPool3() {
     *                      String s = "a";
     *                      String ss = "a";
     *                      String sss = "a";
     *                      ss += "hello";
     *                      sss = sss.replace("a", "c");
     *
     *                      System.out.println(s); // a
     *                      System.out.println(ss); // ahello
     *                      System.out.println(sss); // c
     *                  }
     *
     *          <字符串常量池本质>
     *              字符串常量池 是一个固定大小的Hashtable,默认大小长度是1009;如果字符串常量池放任的String非常多,会造成hash冲突,导致链表很长,调用String.intern时性能大幅下降;
     *
     *              字符串常量池大小:
     *                  JDK6中,字符串常量池是固定的1009;
     *                  JDK7中,字符串常量池 默认是60013,1009是最小值;
     *                  JDK8中,字符串常量池 1009是最小值;
     *
     *                  查看当前进程的大小:
     *                      jps
     *                      jinfo -flag StringTableSize 进程ID
     *
     *                  设置 字符串常量池的长度:
     *                      -XX:StringTableSize
     *
     *          <String内存分配>
     *              在Java语言中有 8种基本数据类型和String;为了使它们在运行过程中速度更快,更节省内存,都提供了 常量池的概念;
     *
     *              String类型的常量池使用方式:
     *                  a,直接使用 "" 声明出来的String对象 会直接存储在 常量池;
     *                      "a";
     *                  b,使用String的intern();
     *
     *              存放位置:
     *                  JDK6及之前,字符串常量池 存放于 堆的永久代;
     *                  JDK7,字符串常量池 在堆中(非永久代);
     *                  JDK8及之后,字符串常量池 在堆中(无永久代);
     *
     *              字符串常量池移到 非永久代的原因:
     *                  jdk 7 将 字符串常量池 移至堆中,因为 永久代的回收效率很低,只有FullGC 的时候 才会 回收 永久代;
     *                  在 开发中 会创建大量的字符串,回收效率太低,导致 永久代 空间不足;
     */

 

/**
     *          <字符串操作>
     *              1、常量与常量的拼接结果 在常量池,原理是 编译期优化;
     *                  eg1:
     *                      private static void testPj1() {
     *
     *                          String s = "a" + "b";
     *                          String ss = "ab";
     *                          System.out.println(s == ss); // true
     *                          System.out.println(s.equals(ss)); // true
     *                      }
     *
     *                       0 ldc #3 <ab>
     *                       2 astore_0
     *                       3 ldc #3 <ab>
     *                       5 astore_1
     *                       6 getstatic #4 <java/lang/System.out>
     *                       ...
     *
     *                      String s = "a" + "b"; 在javac时,已经优化为 String s = "ab";
     *
     *                  eg2:
     *                      final String s1 = "a";
     *                      final String s2 = "b";
     *                      String s3 = "ab";
     *                      String s4 = s1 + s2;
     *                      System.out.println(s3 == s4);// true
     *
     *                       0 ldc #3 <a>
     *                       2 astore_0
     *                       3 ldc #4 <b>
     *                       5 astore_1
     *                       6 ldc #5 <ab>
     *                       8 astore_2
     *                       9 ldc #5 <ab>
     *                      11 astore_3
     *                      ...
     *
     *                      字符串拼接 不一定适用StringBuilder;
     *                      如果拼接符左右2边都是 常量或常量引用,仍适用 编译期优化;
     *
     *              2、常量与变量拼接 结果在 堆中非常量池,原理是 StringBuilder;
     *                  eg:
     *                  private static void testPj2() {
     *
     *                      String s1 = "a";
     *                      String s2 = "b";
     *                      String s3 = "ab";
     *                      String s4 = "a" + "b";
     *
     *                      // 如果拼接符 前后出现变量,相对于在堆中new String()
     *                      String s5 = s1 + "b";
     *                      String s6 = "a" + s2;
     *                      String s7 = s1 + s2;
     *
     *                      System.out.println(s3 == s4);// true
     *                      System.out.println(s3 == s5);// false
     *                      System.out.println(s3 == s6);// false
     *                      System.out.println(s3 == s7);// false
     *                      System.out.println(s5 == s6);// false
     *                      System.out.println(s5 == s7);// false
     *                      System.out.println(s6 == s7);// false
     *
     *                      // 判断字符串常量池中是否存在 "ab" 值,如果存在,返回"ab"的内存地址;
     *                      //                                 如果不存在,在字符串常量池中存储,返回内存地址;
     *                      String s8 = s6.intern();
     *                      System.out.println(s3 == s8);// true
     *
     *                  }
     *
     *                  ***变量拼接的原理
     *                      String s1 = "a";
     *                      String s2 = "b";
     *                      String s3 = s1 + s2;
     *
     *                       0 ldc #3 <a>
     *                       2 astore_0
     *                       3 ldc #4 <b>
     *                       5 astore_1
     *                       6 new #5 <java/lang/StringBuilder>
     *                       9 dup
     *                      10 invokespecial #6 <java/lang/StringBuilder.<init>>
     *                      13 aload_0
     *                      14 invokevirtual #7 <java/lang/StringBuilder.append>
     *                      17 aload_1
     *                      18 invokevirtual #7 <java/lang/StringBuilder.append>
     *                      21 invokevirtual #8 <java/lang/StringBuilder.toString>
     *                      24 astore_2
     *                      25 return
     *
     *                      +拼接符(操作变量)的原理是: StringBuilder
     *
     *                  *** +与StringBuilder性能对比
     *
     *                      count = 100000
     *
     *                      private static void jia(int cout) {
     *                          String s = "";
     *                          for (int i=0; i< cout; i++){
     *                              s += "a";                   // 每次循环都会创建StringBuilder,StringBuilder.toString 又会进行new String
     *                          }
     *                      }
     *
     *                      耗时:6689ms
     *
     *                      private static void stringBuilder(int cout) {
     *                          StringBuilder stringBuilder = new StringBuilder();   // 只会创建一个StringBuilder
     *                          for (int i=0; i< cout; i++){
     *                              stringBuilder.append("a");                      // StringBuilder底层使用char[]实现,stringBuilder.append 操作时需要数组扩容,可以通过构造器预先指定容量进行优化;
     *                          }
     *                      }
     *
     *                      耗时: 8ms
     *
     *              3、调用intern(),若无,会主动将常量池中还没有的字符串对象 放入池中,并返回此对象内存地址;
     *                              若有,直接返回对象内存地址;
     *
     *                  <intern方法>
     *                      what
     *                          A pool of strings, initially empty, is maintained privately by the class String.
     *                          被String类私下维护的 字符串池 初始是空的;
     *                          When the intern method is invoked, if the pool already contains a string equal to this {@code String} object as determined by the {@link #equals(Object)} method, then the string from the pool is returned.
     *                          当intern方法被调用时,如果池中包含指定的字符串值,返回池中的字符串;
     *                          Otherwise, this {@code String} object is added to the pool and a reference to this {@code String} object is returned.
     *                          否则,将会在池中添加字符串值 并返回 字符串值的引用;
     *                          It follows that for any two strings {@code s} and {@code t},{@code s.intern() == t.intern()} is {@code true} if and only if {@code s.equals(t)} is {@code true}.
     *                          对于任意2个字符串,如果intern为true,则equals为true;
     *
     *                      特点
     *                          如果内存中需要大量的字符串,使用intern方法,可以明显降低内存的大小;
     *
     *                      如何保证变量s指向的是 堆中字符串常量池中的数据?
     *                          方式1:字面量
     *                              String s = "a";
     *
     *                          方式2:intern()
     *                              String s = new String("a").intern();
     *                              String s = new StringBuilder("a").toString().intern();
     *
     *                       <new String("a")创建几个对象>
     *                              2个
     *
     *                              eg:
     *                              new String("a");
     *
     *                               0 new #3 <java/lang/String>                            // 堆中创建String类型的对象
     *                               3 dup
     *                               4 ldc #4 <a>                                           // 字符串常量池中的 "a"对象
     *                               6 invokespecial #5 <java/lang/String.<init>>           // 调用 String的构造器 进行属性赋值
     *                               9 pop
     *                              10 return
     *
     *                      <new String("a") + new String("b")创建几个对象>
     *                              6个
     *
     *                              eg:
     *                              new String("a") + new String("b");
     *
     *                               0 new #3 <java/lang/StringBuilder>                         // new StringBuilder
     *                               3 dup
     *                               4 invokespecial #4 <java/lang/StringBuilder.<init>>
     *                               7 new #5 <java/lang/String>                                // new String
     *                              10 dup
     *                              11 ldc #6 <a>                                               // 字符串常量池的 "a"
     *                              13 invokespecial #7 <java/lang/String.<init>>
     *                              16 invokevirtual #8 <java/lang/StringBuilder.append>
     *                              19 new #5 <java/lang/String>                                // new String
     *                              22 dup
     *                              23 ldc #9 <b>                                               // 字符串常量池的 "b"
     *                              25 invokespecial #7 <java/lang/String.<init>>
     *                              28 invokevirtual #8 <java/lang/StringBuilder.append>
     *                              31 invokevirtual #10 <java/lang/StringBuilder.toString>     // StringBuilder.toString底层new String
     *                              34 astore_0                                                     【注意】:此时字符串常量池中没有 "ab"
     *                              35 return
     *
     *
     */

  

  

图解String.intern()

1、字符串常量池预先不存在"ab"

jdk6

 

 

jdk7及后 

 

 

2、字符串常量池预先存在"ab"

 

/**
     *  【字符串常量池-GC】
     *      查看
     *          -XX:+PrintStringTableStatistics -XX:+PrintGCDetails
     */

  

 

  

posted on 2022-04-28 17:08  anpeiyong  阅读(50)  评论(0编辑  收藏  举报

导航