Java知识学习——方法区(二)

运行时常量池

一个二进制字节码文件包括了三个部分:类的基本信息、常量池、类方法定义(包括了虚拟机指令)。如果想查看一个类的二进制字节码的信息,可以使用Javap -v 字节码文件 来查看相应的信息。下面就是一个简单的Helloword的Java Demo对应的二进制字节码信息。

Classfile /F:/JavaProject/day_12/src/_01Calender/SayHello.class
  Last modified 2020-2-23; size 421 bytes
  MD5 checksum e5bd88fa5c10f13df7396dc70d7759f7
  Compiled from "SayHello.java"
public class SayHello
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#15         // java/lang/Object."<init>":()V
   #2 = Fieldref           #16.#17        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #18            // Hello world
   #4 = Methodref          #19.#20        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class              #21            // SayHello
   #6 = Class              #22            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               main
  #12 = Utf8               ([Ljava/lang/String;)V
  #13 = Utf8               SourceFile
  #14 = Utf8               SayHello.java
  #15 = NameAndType        #7:#8          // "<init>":()V
  #16 = Class              #23            // java/lang/System
  #17 = NameAndType        #24:#25        // out:Ljava/io/PrintStream;
  #18 = Utf8               Hello world
  #19 = Class              #26            // java/io/PrintStream
  #20 = NameAndType        #27:#28        // println:(Ljava/lang/String;)V
  #21 = Utf8               SayHello
  #22 = Utf8               java/lang/Object
  #23 = Utf8               java/lang/System
  #24 = Utf8               out
  #25 = Utf8               Ljava/io/PrintStream;
  #26 = Utf8               java/io/PrintStream
  #27 = Utf8               println
  #28 = Utf8               (Ljava/lang/String;)V
{
  public SayHello();
    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 2: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String Hello world
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 5: 0
        line 6: 8
}
SourceFile: "SayHello.java"

在整个解析的最后,有关于main方法的一个说明,包括参数、访问类型,以及最终的虚拟机指令。首先看虚拟机指令的第一行“getstatic #2”,解释器在将二进制字节码进行解释的时候其实也只是认这一个内容,#2代表要去常量池Costant pool中的#2去查找,对应的是一个FieldRef,也就是变量引用,又对应#16和#17的信息,然后又通过#16和#17后面的内容确定最终要拿到PrintStrem这个变量。然后再来执行第二行虚拟机指令——“ldc #3”,在Constant Pool找到#3 看到是一个String 类型的数据对应#18的内容,找到#18行然后发现是字符串“Hello World”。

所谓常量池,就是一张表,虚拟机指令根据这张表去根据这张常量表找到要执行的类名、方法名、参数类型、字面量(就像上面的Hello world字符串、整数、布尔类型)的信息。

运行时常量池,常量池是*.class文件,当该类被加载,它的常量池信息就会被加载到运行时常量池,并把里面的符号地址变成真实地址。

 

常量池与StringTable(串池)

首先看一个简单字符串的二进制文件。

通过Javap得到的二进制文件为:

常量池中的内容,在运行时都会被加载到运行时常量池,这时a,b,ab都是常量池的符号,还不是Java字符串对象,此时它们就像#2、#3、#4这种符号。

执行到“ldc #2”时,会把a符号变成“a”字符串对象,在变为“a”字符串对象好之后,它会准备好一块空间,这块空间就叫做StringTable,也就是串池,刚开始它是空的,没有任何内容。在变为“a”字符串对象好之后,会把“a”做为key,去StringTable找有没有相同取值的Key(StringTable的数据结构其实就是hash表,且长度是固定,也不能扩容的),如果没有找到,就会把刚刚生成的“a”字符串对象放入串池,如果有就直接使用串池中的对象。总之,串池中的字符串对象只能存在一份。

我们经常会碰到下面的一段代码:

虽然s4和s5的内容是一致的,都是“ab”,但是具体的位置却不相同,通过Javap反编译就可以明白,其反编译的结果如下:

         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: new           #5                  // class java/lang/StringBuilder
        12: dup
        13: invokespecial #6                  // Method java/lang/StringBuilder."<init>":()V
        16: aload_1
        17: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        20: aload_2
        21: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        24: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        27: astore        4
        29: ldc           #4                  // String ab
        31: astore        5
        33: return

上面的二进制文件中从第9到第27都是String s4=s1+s2的二进制文件的内容,通过分析这句话其实是先找到StringBuilder类,创建了一个StringBuilder对象,调用了两次Append对象,然后又调用StringBuilder的ToString(),通过查看源码发现StringBuidler中的ToString()方法其实是new String(value, 0, count)即创建了一个对象,在前面我们了解到,通过new关键字创建的都是放在了堆中,所以就这一行,其实是做了很多事(new StringBuilder().append("a").append("b").toString() => new String("ab"))。从29到31其实就是String s5="a"+"b",其实是JavaC在编译期间的优化,结果在编译期间已经确定为了“ab”,都是常量,在编译期间都知道是常量“ab”,而上面的s1和s2是变量,在编译期间并不能确定。所以两行虽然值都是一样,但是最终的来源是不一样的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

黑白键的约定

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值