25.jvm内存结构部分——方法区_常量池

我们就来学习运行时常量池。
在之前呢我们介绍方法区的组成时,我们提到过运行时常量池,这个图少写两个字"运行"
那么不管是1.6还是1.8,它们在方法区域的组成中都有一个叫运行时常量池的部分。

那这个运行时常量池内部还有可能包含一个叫stringTable的东西。

那它到底是什么呢?我们要讲解运行时常量池,就要聊聊什么叫常量池
这里我通过一个例子来给大家解释常量池的含义。先来看一下这段代码。

 这个代码相信大家都非常的熟悉啊,就是一个很简单的hello world 这样一个程序啊,
那是hello world要运行,你肯定先把它编译成二进制的字节码,编译成二进制字节码。
这个字节码它有哪些部分组成呢?

一般来说字节码由这三部分组成【大的方向】
第一部分是这个类的基本信息
第二部分是类的一个常量池
第三部分是类中的一些方法定义,这些的方法定义中就包含了虚拟机指令

那我们下面呢就针对这三部分先来看一下。

那怎么看呢?二进制字节码都是二进制的你也看不懂。
那我们可以拿到它这个class。借助jdk提供的一个工具,把这个class 再做一次反编译。反编译后的结果,我们人类就可以勉强读懂了。

好,那下面我们先进入到这个class 的所在目录,
接着我们使用jdk中的javap来反编译刚才的这个Client.class这个字节码。-v参数是显示这个详细信息.(javap -v Client.class

Classfile /C:/Users/89296/IdeaProjects/studyjava/target/classes/design/wlb/studyjava/demo/restudy/
c4jvm专题/chapter02/c25方法区_方法区_常量池/Client.class
  Last modified 2022-6-25; size 729 bytes
  MD5 checksum 957a44a38b4099f657d2703f4cd0fce1
  Compiled from "Client.java"
public class design.wlb.studyjava.demo.restudy.c4jvm专题.chapter02.c25方法区_方法区_常量池.Client
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#21         // java/lang/Object."<init>":()V
   #2 = Fieldref           #22.#23        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #24            // hello world
   #4 = Methodref          #25.#26        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class              #27            // design/wlb/studyjava/demo/restudy/c4jvm专题/chapter02
/c25方法区_方法区_常量池/Client
   #6 = Class              #28            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Ldesign/wlb/studyjava/demo/restudy/c4jvm专题/chapter02/c25方法区_方法区
_常量池/Client;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               args
  #17 = Utf8               [Ljava/lang/String;
  #18 = Utf8               MethodParameters
  #19 = Utf8               SourceFile
  #20 = Utf8               Client.java
  #21 = NameAndType        #7:#8          // "<init>":()V
  #22 = Class              #29            // java/lang/System
  #23 = NameAndType        #30:#31        // out:Ljava/io/PrintStream;
  #24 = Utf8               hello world
  #25 = Class              #32            // java/io/PrintStream
  #26 = NameAndType        #33:#34        // println:(Ljava/lang/String;)V
  #27 = Utf8               design/wlb/studyjava/demo/restudy/c4jvm专题/chapter02/c25方法区_方法区_
常量池/Client
  #28 = Utf8               java/lang/Object
  #29 = Utf8               java/lang/System
  #30 = Utf8               out
  #31 = Utf8               Ljava/io/PrintStream;
  #32 = Utf8               java/io/PrintStream
  #33 = Utf8               println
  #34 = Utf8               (Ljava/lang/String;)V
{
  public design.wlb.studyjava.demo.restudy.c4jvm专题.chapter02.c25方法区_方法区_常量池.Client();
    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 4: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Ldesign/wlb/studyjava/demo/restudy/c4jvm专题/chapter02/c25方法
区_方法区_常量池/Client;

  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/St
ring;)V
         8: return
      LineNumberTable:
        line 6: 0
        line 7: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  args   [Ljava/lang/String;
    MethodParameters:
      Name                           Flags
      args
}
SourceFile: "Client.java"
 


还挺多的,那么我刚才说了,主要部分有三部分,类的基本信息
哪部分是类的基本信息呢?以下这一部分

Classfile /C:/Users/89296/IdeaProjects/studyjava/target/classes/design/wlb/studyjava/demo/restudy/
c4jvm专题/chapter02/c25方法区_方法区_常量池/Client.class  类的文件
  Last modified 2022-6-25; size 729 bytes           类的修改时间
  MD5 checksum 957a44a38b4099f657d2703f4cd0fce1        签名
  Compiled from "Client.java"                【类的源java文件】
public class design.wlb.studyjava.demo.restudy.c4jvm专题.chapter02.c25方法区_方法区_常量池.Client                   类的访问修饰符、包名、类名
  minor version: 0         类的版本
  major version: 52        【52是内部版本对应jdk1.8】
  flags: ACC_PUBLIC, ACC_SUPER      类的访问修饰符


 再往下看,从这儿开始constant pool,这就是我们说的常量池,常量池里面都是一些地址,再加一些符号,待会再解释。我们先往下看类的方法定义。

Constant pool:
   #1 = Methodref          #6.#21         // java/lang/Object."<init>":()V
   #2 = Fieldref           #22.#23        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #24            // hello world
   #4 = Methodref          #25.#26        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class              #27            // design/wlb/studyjava/demo/restudy/c4jvm专题/chapter02
/c25方法区_方法区_常量池/Client
   #6 = Class              #28            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Ldesign/wlb/studyjava/demo/restudy/c4jvm专题/chapter02/c25方法区_方法区
_常量池/Client;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               args
  #17 = Utf8               [Ljava/lang/String;
  #18 = Utf8               MethodParameters
  #19 = Utf8               SourceFile
  #20 = Utf8               Client.java
  #21 = NameAndType        #7:#8          // "<init>":()V
  #22 = Class              #29            // java/lang/System
  #23 = NameAndType        #30:#31        // out:Ljava/io/PrintStream;
  #24 = Utf8               hello world
  #25 = Class              #32            // java/io/PrintStream
  #26 = NameAndType        #33:#34        // println:(Ljava/lang/String;)V
  #27 = Utf8               design/wlb/studyjava/demo/restudy/c4jvm专题/chapter02/c25方法区_方法区_
常量池/Client
  #28 = Utf8               java/lang/Object
  #29 = Utf8               java/lang/System
  #30 = Utf8               out
  #31 = Utf8               Ljava/io/PrintStream;
  #32 = Utf8               java/io/PrintStream
  #33 = Utf8               println
  #34 = Utf8               (Ljava/lang/String;)V


我们先来看第三部分类的方法定义 

{
  public design.wlb.studyjava.demo.restudy.c4jvm专题.chapter02.c25方法区_方法区_常量池.Client();        【第一个是咱们的构造方法】
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1                        【虚拟机指令0 1 4】
         0: aload_0                                                                                             
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 4: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Ldesign/wlb/studyjava/demo/restudy/c4jvm专题/chapter02/c25方法区_方法区_常量池/Client;

  public static void main(java.lang.String[]); 【第二个是咱们的main方法】
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1                        【虚拟机指令0 3 5】
         0: getstatic     #2                 // Field java/lang/System.out:Ljava/io/PrintStream; (获取一个静态变量) ---加载了#2 【System.out】 静态变量
         3: ldc           #3                  // String hello world   (加载一个参数)加载了#3 【hello world】参数
         5: invokevirtual #4             // Method java/io/PrintStream.println:(Ljava/lang/St
ring;)V                (执行一次虚方法调用) 执行了#4 [print方法]
         8: return        (执行return) 表示main方法执行结束了。
      LineNumberTable:
        line 6: 0
        line 7: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  args   [Ljava/lang/String;
    MethodParameters:
      Name                           Flags
      args
}
SourceFile: "Client.java"

这个方法的定义就包括了虚拟机的指令了,

所以java源代码变成虚拟机指令就是这么几条指令(main方法)

 0: getstatic     #2      // 略

 3: ldc           #3         // 略

 5: invokevirtual #4   // 略

 8: return 

上面虚拟机指令后面有一些注释,其实最后面这些注释啊是我们的javap程序,帮我们加上的。真正在cpu,在我们之前讲过java 有一个解释器,它去执行这些翻译这些虚拟机指令的时候,它看到的只有没加注释的这几行指令。

 0: getstatic     #2   

 3: ldc           #3

 5: invokevirtual #4      

 8: return 

那这个解释器怎么解释呢?

它在解释的过程中,它就要把后面这个#2、#3、#4真正进行一个查表翻译.

那举个例子吧,比如说我们这个getstatic  #2,这个井号二到底代表啥呢?

我要获取哪个静态变量呢?这就得去查那个常量池的表

Constant pool:
   #2 = Fieldref           #22.#23        // java/lang/System.out:Ljava/io/PrintStream;
【#2 是FieldReference】 它去引用了一个成员变量,引用的是哪一个成员变量呢?

它又找到了#22.#23 ,#22是一个类名,我要找哪个类的成员变量呢?  #29 Class java/lang/System,噢就是 java/lang/System这个类的成员变量,哪个成员变量呢?#23

引用了#30:#31   #30 out变量 #31 Ljava/io/PrintStream 对out的一个类型说明
  #22 = Class              #29           
  #23 = NameAndType        #30:#31       

  #29 = Utf8               java/lang/System
  #30 = Utf8               out
  #31 = Utf8               Ljava/io/PrintStream;


  #32 = Utf8               java/io/PrintStream
  #33 = Utf8               println
  #34 = Utf8               (Ljava/lang/String;)V

所以回到刚才的#2,我们就知道了 其实是 #29.#30:#31

也就是 java/lang/System.out:Ljava/io/PrintStream;

getstatic 就是找某个类的成员变量,就找到了System类下面的(printStream类型的)out变量。


上面读懂了,我们继续来看看

ldc           #3                【ldc找到一个引用地址】,找#3的引用地址

Constant pool:

        #3 = String             #24           

        #24 = Utf8               hello world        【是一个utf8的字符串hello world,这里的utf8是虚拟机常量池中的一种类型。就把它理解为一个字符串吧】

把hello world变成一个字符串对象,加载进来(ldc) ----- ldc hello world


再往下走,就是invokevirtual

invokevirtual #4   【invokevirtual执行一次虚方法调用】,执行#4的虚方法调用。

Constant pool:

   #4 = Methodref          #25.#26        // java/io/PrintStream.println:(Ljava/lang/String;)V.

Methodref 是一个方法引用

  #25 = Class              #32            // java/io/PrintStream
  #26 = NameAndType        #33:#34        // println:(Ljava/lang/String;)V

  #32 = Utf8               java/io/PrintStream

  #33 = Utf8               println
  #34 = Utf8               (Ljava/lang/String;)V

即 #4 -->  #25.#26  --->  #32.#33:#34     invokevirtual  #32.#33:#34    PrintStream.println

噢执行了一次,PrintStream中的println方法。


 所以常量池的作用就是给我们这些指令提供一些常量符号,根据这个常量符号,以查表的方式,去找到它们,这样你的虚拟机指令才能成功的去执行。

上一篇:24.jvm内存结构部分——方法区_内存溢出2_tgbyhn31的博客-CSDN博客

下一篇:26.jvm内存结构部分——方法区_运行时常量池_tgbyhn31的博客-CSDN博客

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值