Java中的StringTable常量池

发布于:2023-01-06 ⋅ 阅读:(367) ⋅ 点赞:(0)

一. StringTable常量池与串池的关系

代码:

public class Demo6 {
    public static void main(String[] args) {
        String s1 = "a";
        String s2 = "b";
        String s3 = "ab";
    }
}

反编译后:

"C:\Program Files\Java\jdk-11.0.16.1\bin\javap.exe" -v jvm1.Demo6
Classfile /C:/untitled/out/production/myLife/jvm1/Demo6.class
  Last modified 2022823; size 477 bytes
  MD5 checksum ef11e54131b46d597626bc992d2704d3
  Compiled from "Demo6.java"
public class jvm1.Demo6
  minor version: 0
  major version: 55
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #5                          // jvm1/Demo6
  super_class: #6                         // java/lang/Object
  interfaces: 0, fields: 0, methods: 2, attributes: 1
Constant pool:
   #1 = Methodref          #6.#24         // java/lang/Object."<init>":()V
   #2 = String             #25            // a
   #3 = String             #26            // b
   #4 = String             #27            // ab
   #5 = Class              #28            // jvm1/Demo6
   #6 = Class              #29            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Ljvm1/Demo6;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               args
  #17 = Utf8               [Ljava/lang/String;
  #18 = Utf8               s1
  #19 = Utf8               Ljava/lang/String;
  #20 = Utf8               s2
  #21 = Utf8               s3
  #22 = Utf8               SourceFile
  #23 = Utf8               Demo6.java
  #24 = NameAndType        #7:#8          // "<init>":()V
  #25 = Utf8               a
  #26 = Utf8               b
  #27 = Utf8               ab
  #28 = Utf8               jvm1/Demo6
  #29 = Utf8               java/lang/Object
{
  public jvm1.Demo6();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Ljvm1/Demo6;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=4, args_size=1
         0: ldc           #2                  // String a
         2: astore_1
         3: ldc           #3                  // String b
         5: astore_2
         6: ldc           #4                  // String ab
         8: astore_3
         9: return
      LineNumberTable:
        line 5: 0
        line 6: 3
        line 7: 6
        line 8: 9
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      10     0  args   [Ljava/lang/String;
            3       7     1    s1   Ljava/lang/String;
            6       4     2    s2   Ljava/lang/String;
            9       1     3    s3   Ljava/lang/String;
}
SourceFile: "Demo6.java"

Process finished with exit code 0

  • 问题分析:
    在这里插入图片描述

注意:

  1. StringTable属于Hashtable结构,不能扩容
  2. Constant pool中的信息,都会被加载到运行时常量池中,这是a b ab都是常量池中的符号,还没有变为java字符串对象
  3. 例如: 0: ldc #2 会把a符号变为"a"字符串对象(而是用到这行代码才开始创建)此时还要准备StringTable串池[]如果第一次找串池中没有本身对象,那么就要放入串池中为[“a”]。但是如果串池中有,那么只调用
  4. 不是每个字符串对象事先放在字符串池里,而是用到这行代码才开始创建(属于懒惰行为)

二. StringTable字符串的拼接

问题分析
在这里插入图片描述

  • 问:System.out.println(s3==s4);?false
    原因:s3属于串池中,s4属于堆中,虽然值一样,但是位置不一样
    在这里插入图片描述

代码

package jvm1;
public class Demo6 {
    public static void main(String[] args) {
        String s1 = "a";
        String s2 = "b";
        String s3 = "ab";
        String s4 = s1 + s2;
    }
}

源码分析



  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=5, args_size=1
         0: ldc           #2                  // String a
         2: astore_1
         3: ldc           #3                  // String b
         5: astore_2
         6: ldc           #4                  // String ab
         8: astore_3
         9: aload_1
        10: aload_2
        11: invokedynamic #5,  0              // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
        16: astore        4
        18: return
      LineNumberTable:
        line 4: 0
        line 5: 3
        line 6: 6
        line 7: 9
        line 9: 18
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      19     0  args   [Ljava/lang/String;
            3      16     1    s1   Ljava/lang/String;
            6      13     2    s2   Ljava/lang/String;
            9      10     3    s3   Ljava/lang/String;
           18       1     4    s4   Ljava/lang/String;
}

三. StringTable编译期优化

代码

package jvm1;
public class Demo6 {
    public static void main(String[] args) {
        String s1 = "a";
        String s2 = "b";
        String s3 = "ab";
        String s5="a"+"b";
        //String s4 = s1 + s2;//new StringBuider().append("a").append("b").toString();
    }
}

问题分析
在这里插入图片描述

  • javac 在编译期间的优化,结果已经在编译期间确定为"ab"

四. StringTable字符串延迟加载

为了查看字符串及其常量池中的变化,这里我采取了IDEA中的调式功能中的Memory(内存问题)子功能

  1. 调试1
    在这里插入图片描述

  2. 调试2
    在这里插入图片描述

  3. 调试3
    在这里插入图片描述

  • 说明字符串字面量也是(延迟)成为对象的

五. StringTable特性

  • 常量池中的字符串仅是符号,第一次用到时才变为对象

  • 利用串池的机制,来避免重复创建字符串对象,串池中的字符串有且仅有一个

  • 字符串变量拼接的原理是 StringBuilder (JDK1.8)

  • 字符串常量拼接的原理是编译期优化(编译即确定)

  • 可以使用 intern 方法,主动将串池中还没有的字符串对象放入串池

  • 1.8 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串
    池中的对象返回
    在这里插入图片描述

  • 1.6 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有会把此对象复制一份,
    放入串池, 会把串池中的对象返回

六. StringTable面试题

package jvm1;

public class Demo5 {
    public static void main(String[] args) {
        String s1 = "a";
        String s2 = "b";
        String s3 = "a" + "b";//ab
        String s4 = s1 + s2;//new String("ab")
        String s5 = "ab";
        String s6 = s4.intern();//由于串池中存入"ab",所以s4还在指向堆中,但是新生成s6是指向串池中的

// 问
        System.out.println(s3 == s4);//false
        System.out.println(s3 == s5);//true
        System.out.println(s3 == s6);//true

        String x2 = new String("c") + new String("d");//new String("cd")
        String x1 = "cd";//"cd"   此时串池中有["cd"]  
        x2.intern();//所以x2返回的值不能放入串池中,仍为堆中
        System.out.println(x1 == x2);//false
        
        
// 问,如果调换了【最后两行代码】的位置呢,如果是jdk1.6呢 System.out.println(x1 == x2);
        System.out.println(x1 == x2);//true

    }
}

七. StringTable位置

在这里插入图片描述
在这里插入图片描述

PerGen 永久代(JDK1.6)
在这里插入图片描述

Metaspace 元空间(JDK1.8)

  • 实例1
    在这里插入图片描述
  • 实例二(解决后)
  • 牵扯的代码
package cn.itcast.jvm.t1.stringtable;

import java.util.ArrayList;
import java.util.List;

/**
 * 演示 StringTable 位置
 * 在jdk8下设置 -Xmx10m -XX:-UseGCOverheadLimit
 * 在jdk6下设置 -XX:MaxPermSize=10m
 */
public class Demo1_6 {

    public static void main(String[] args) throws InterruptedException {
        List<String> list = new ArrayList<String>();
        int i = 0;
        try {
            for (int j = 0; j < 260000; j++) {
                list.add(String.valueOf(j).intern());
                i++;
            }
        } catch (Throwable e) {
            e.printStackTrace();
        } finally {
            System.out.println(i);
        }
    }
}

八. StringTable垃圾回收

演示StringTable垃圾回收

  1. -Xmx10m 设置JVM虚拟机的堆的值
  2. -XX:+PrintStringTableStatistics 打印字符串表的统计信息,可以清楚看到字符串实例的个数和占用的大小信息
  3. XX:+PrintGCDetails -verbose:gc 显示垃圾回收的次数时间等
  • 分析1
    在这里插入图片描述

  • 分析2
    在这里插入图片描述

  • 分析3
    在这里插入图片描述

通过分析案例中,我们证明了StringTable也会发生垃圾回收

  • 演示的代码
package cn.itcast.jvm.t1.stringtable;

import java.util.ArrayList;
import java.util.List;

/**
 * 演示 StringTable 垃圾回收
 * -Xmx10m -XX:+PrintStringTableStatistics -XX:+PrintGCDetails -verbose:gc
 */
public class Demo1_7 {
    public static void main(String[] args) throws InterruptedException {
        int i = 0;
        try {
            for (int j = 0; j < 100000; j++) { // j=100, j=10000
                String.valueOf(j).intern();
                i++;
            }
        } catch (Throwable e) {
            e.printStackTrace();
        } finally {
            System.out.println(i);
        }

    }
}

八. StringTable性能调优

  1. 调整 -XX:StringTableSize=桶个数
    案例:
  • 分析1
    在这里插入图片描述

  • 分析2
    在这里插入图片描述

如果字符串常量个数非常多,那么就可以把-XX:StringTableSize调的非常大,这样就有更好的哈希分布,减少哈希冲突,能够提高性能

  • 代码
package jvm1;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
    /*
      演示串池大小对性能的影响
      -Xms500m -Xmx500m -XX:+PrintStringTableStatistics -XX:StringTableSize=1009
     */
    public class Demo7 {

        public static void main(String[] args) throws IOException {
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream("c:\\a\\sss.txt"), "utf-8"))) {
                String line = null;
                long start = System.nanoTime();
                while (true) {
                    line = reader.readLine();
                    if (line == null) {
                        break;
                    }
                    line.intern();
                }
                System.out.println("cost:" + (System.nanoTime() - start) / 1000000);
            }


        }
    }

在这里插入图片描述

  1. 考虑将字符串对象是否入池
本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到