美文网首页程序员
讲一讲Java的字符串常量池,看完你的思路就清晰了

讲一讲Java的字符串常量池,看完你的思路就清晰了

作者: 前程有光 | 来源:发表于2020-11-09 13:50 被阅读0次

    前言

    很多朋友Java的字符串常量池的概念困扰了很长一段时间,最近研究了一下jvm指令码,终于对它有了大概的了解。 在展示案例前,我们需要先搞清楚一个概念,众所周知,jvm的内存模型由程序计数器、虚拟机栈、本地方法栈、堆、元空间(方法区)、直接内存组成。 今天我们谈到的概念只和虚拟机栈、堆、元空间(方法区)有关。 先举个例子说明两种关于字符串最基本的使用情况:

    • String s =“abc”;在编译期间,会将等号右边的“abc”常量放在常量池中,在程序运行时,会将s变量压栈,栈中s变量直接指向元空间的字符串常量池abc项,没有经过堆内存。

    • String s = new String(“abc”);在编译期间,会将等号右边的“abc”常量放在常量池中,在程序运行时,先在堆中创建一个String对象,该对象的内容指向常量池的“abc”项。然后将s变量压栈,栈中s变量指向堆中的String对象。

    下面通过javap -v xxx.class命令来查看class文件的指令码,通过分析这些指令码更确切的了解我们想知道的问题。笔者的jdk是1.8版本的,版本不同可能效果也不同。

    第一个例子:String a = “abc”

    首先展示我们的源代码

    public class StringCodeTest {
        public static void main(String[] args) {
                String a = "abc";
                System.out.println(a);
            }
       }
    
    

    我们执行 javap -v StringCodeTest .class>StringCodeTest.txt命令,将指令码放到StringCodeTest.txt中。

    Classfile /C:/Users/zhiyi/IdeaProjects/springboottest/target/classes/cn/lizy/service/StringCodeTest.class
      Last modified 2020-11-7; size 610 bytes
      MD5 checksum 2adeba87a0f1b315019efe540bb058cd
      Compiled from "StringCodeTest.java"
    public class cn.lizy.service.StringCodeTest
      minor version: 0
      major version: 52
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
       #1 = Methodref          #6.#22         // java/lang/Object."<init>":()V
       #2 = String             #23            // abc
       #3 = Fieldref           #24.#25        // java/lang/System.out:Ljava/io/PrintStream;
       #4 = Methodref          #26.#27        // java/io/PrintStream.println:(Ljava/lang/String;)V
       #5 = Class              #28            // cn/lizy/service/StringCodeTest
       #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               Lcn/lizy/service/StringCodeTest;
      #14 = Utf8               main
      #15 = Utf8               ([Ljava/lang/String;)V
      #16 = Utf8               args
      #17 = Utf8               [Ljava/lang/String;
      #18 = Utf8               a
      #19 = Utf8               Ljava/lang/String;
      #20 = Utf8               SourceFile
      #21 = Utf8               StringCodeTest.java
      #22 = NameAndType        #7:#8          // "<init>":()V
      #23 = Utf8               abc
      #24 = Class              #30            // java/lang/System
      #25 = NameAndType        #31:#32        // out:Ljava/io/PrintStream;
      #26 = Class              #33            // java/io/PrintStream
      #27 = NameAndType        #34:#35        // println:(Ljava/lang/String;)V
      #28 = Utf8               cn/lizy/service/StringCodeTest
      #29 = Utf8               java/lang/Object
      #30 = Utf8               java/lang/System
      #31 = Utf8               out
      #32 = Utf8               Ljava/io/PrintStream;
      #33 = Utf8               java/io/PrintStream
      #34 = Utf8               println
      #35 = Utf8               (Ljava/lang/String;)V
    {
      public cn.lizy.service.StringCodeTest();
        descriptor: ()V
        flags: 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   Lcn/lizy/service/StringCodeTest;
    
      public static void main(java.lang.String[]);
        descriptor: ([Ljava/lang/String;)V
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=2, locals=2, args_size=1
             0: ldc           #2                  // String abc
             2: astore_1
             3: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
             6: aload_1
             7: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
            10: return
          LineNumberTable:
            line 5: 0
            line 6: 3
            line 24: 10
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0      11     0  args   [Ljava/lang/String;
                3       8     1     a   Ljava/lang/String;
    }
    SourceFile: "StringCodeTest.java"
    
    

    关键要看Constant pool:(这个就是常量池)和main方法。

    main方法中ldc #2 的意思是把常量池中的#2项压入到栈,而#2关联的#23就是“abc”常量。这就说明栈中的对象是直接指向了常量池的。String s = “abc”只生成了一个字符串对象(常量池中的对象)和一个栈中的引用

    第二个例子 String s = new String(“abc”);

    源代码

    public class StringCodeTest {
        public static void main(String[] args) {
                String b = new String("abc");
                System.out.println(b)
            }
       }
    
    

    为了节省篇幅,第二个例子和第三个例子的jvm指令码就不再展示了,只截图关键点,有需要可以自己通过javap命令生成


    main方法中先new #2, 而#2关联的#25就是String对象, 然后再ldc #3(关联的#26就是“abc”常量)也就是说,在执行时,先在堆中创建String对象,再把常量池中的#3项压入到栈(这里的栈应该是操作数栈,参考最上面的内存模型图)供String对象使用。所以,栈中的引用指向了堆中的String对象,String对象指向了常量池中的“abc”项,String s = new String(“abc”)生成了一个字符串对象(常量池中的对象)、一个堆中的String对象和一个栈中的引用。

    第三个例子:String c = new String(“abc”)+“abc”;

    源代码

    public class StringCodeTest {
        public static void main(String[] args) {
                String c = new String("abc")+"abc";
                System.out.println(c);
            }
       }
    
    

    main方法中先创建了Stringbuiler对象,然后创建了String对象(就是new String("abc")), 再执行ldc #3将常量池中的字符串压入到栈。最后做计算,过程和第二个例子差不多,只不过多了个Stringbuiler的append操作。

    总结:理解了大体的原理后,再遇到像 ==判断字符串相等,或者计算String s = new String(“abc”)再内存中创建了几个对象这样的问题时思路就清晰了

    最后

    感谢你看到这里,文章有什么不足还请指正,觉得文章对你有帮助的话记得给我点个赞,每天都会分享java相关技术文章或行业资讯,欢迎大家关注和转发文章!

    相关文章

      网友评论

        本文标题:讲一讲Java的字符串常量池,看完你的思路就清晰了

        本文链接:https://www.haomeiwen.com/subject/drplbktx.html