JVM_1.6_运行时内存区域_运行时常量池

运行时内存区域这块,如果不将内存各个区域做什么的了解清楚,后面看的会很累。

之前将JVM运行时内存区域的内容,整理在了一篇文章中。

在后续深入、细致的学习中,整理的内容越来越多,一篇的话,会导致篇幅过长。

所以将《JVM运行时内存区域详解》分为以下几个章节:

JVM_1.0_运行时内存区域

JVM_1.1_运行时内存区域_堆

JVM_1.2_运行时内存区域_Java虚拟机栈

JVM_1.3_运行时内存区域_方法区

JVM_1.4_运行时内存区域_本地方法栈

JVM_1.5_运行时内存区域_程序计数器

JVM_1.6_运行时内存区域_运行时常量池

JVM_1.7_运行时内存区域_栈帧

 

这里将《Java虚拟机规范中文版》上传了,点击下面链接,即可下载

Java虚拟机规范SE7中文版下载

 

目录

运行时常量池

《深入理解Java虚拟机:JVM高级特性与最佳实践》

《Java Virtual Machine Specification Java SE 7 中文版》

《Java Virtual Machine Specification Java SE 7 中文版 - 第五章 加载、链接与初始化》

什么是字面量

什么是符号引用


 

运行时常量池

《深入理解Java虚拟机:JVM高级特性与最佳实践》

运行时常量池(Runtime Constant Pool)是方法区的一部分

Class文件中除了有类版本、字段、方法、接口等描述信息外,还有一项信息是常量池Constant Pool

(注意:这里的常量池是Class文件中的,可以理解为Class文件常量池,每个Class文件都有,不要和JVM中的常量池混淆)

Java 虚拟机指令执行时不依赖与类、接口,实例或数组的运行时布局而是依赖常量池(constant_pool)表中的符号信息

当类或接口创建时,它的二进制表示中的 constant_pool 表被用来构造运行时常量池。运行时常量池中的所有引用最初都是符号引用。

以上,可以理解,类加载完成之后,所有类中的constant pool中的符号表,被添加到Runtime Constant Pool(运行时常量池)中。

对于运行时常量池,Java虚拟机规范并没有做任何细节的要求,不同厂商不同实现。

不过一般来说,除了保存Class文件中描述的符号应用外,还会把翻译出来的直接引用也存储在运行时常量池中。

Java语言并不要求常量一定只能在编译期产生,也就是并非预置入Class文件中常量池的内容才能进入方法区运行时常量池。

运行期间也可能将新的常量放入池中,比如:String类的intern()方法

 

 

《Java Virtual Machine Specification Java SE 7 中文版》

运行时常量池(Runtime Constant Pool)是每一个类或接口的常量池(Constant_Pool)的运行时表示形式。

它包含了若干种不同的常量:从编译期可知的数值字面量必须运行期解析后才能获得的方法或字段引用

运行时常量池扮演了类似传统语言中符号表(Symbol Table)的角色,不过它存储数据范围比通常意义上的符号表要更为广泛。

每一个运行时常量池都分配在 Java 虚拟机的方法区之中,在类和接口被加载到虚拟机后,对应的运行时常量池就被创建出来。

当创建类或接口的时候,如果构造运行时常量池所需要的内存空间超过了方法区所能提供的最大值,那 Java 虚拟机将会抛出一个 OutOfMemoryError 异常。

 

 

《Java Virtual Machine Specification Java SE 7 中文版 - 第五章 加载、链接与初始化》

Java 虚拟机每个类型都维护一个常量池

它是 Java 虚拟机中的运行时数据结构,像传统编程语言实现中的符号表一样有很多用途。

 

摘录书中关于字符常量的内容:

CONSTANT_String_info 结构包含了由Unicode 码点(Code points)序列来组成字符常量。

Java语言需要全局统一的字符常量(这就意味着如果不同字面量(Literal)包含着相同的码点序列,就必须引用着相同的 String 类的实例) 。

此外,在任意字符串上调用 String.intern 方法,如果那个字符串是字面量的话,方法的结果应当是对相同字面量的 String 实例的引用。因此:("a" + "b" + "c").intern()  == "abc"  必须返回true。

不同字面量,如果是相同的Unicode码点序列,就会引用相同的实例。

举个栗子:

String str1 = "abc";

String str2 = "a"+"b"+"c";

str2.intern() == str1;

class编译之后,str1 和 str2 都是"abc"

根据这个栗子,再品味下上面的话:

这就意味着如果不同字面量(Literal)包含着相同的码点序列,就必须引用着相同的 String 类的实例

 

附件:常量池类型

CONSTANT_Class

用于表示类或接口

CONSTANT_Fieldref、CONSTANT_Methodref、CONSTANT_InterfaceMethodref

字段,方法和接口方法

CONSTANT_String

用于表示 java.lang.String 类型的常量对象

CONSTANT_Integer、CONSTANT_Float

表示 4 字节( int 和 float)的数值常量

CONSTANT_Long、CONSTANT_Double

表示 8 字节( long 和 double)的数值常量

CONSTANT_NameAndType

用于表示字段或方法(和上面的不是同一个)

CONSTANT_Utf8        

用于表示字符串常量的值

CONSTANT_MethodHandle

用于表示方法句柄

CONSTANT_MethodType

用于表示方法类型

CONSTANT_InvokeDynamic

用于表示 invokedynamic 指令所使用到的引导方法( Bootstrap Method)、

引导方法使用到动态调用名称( Dynamic Invocation Name)、参数和请求返回类型、

以及可以选择性的附加被称为静态参数( Static Arguments) 的常量序列。

 

 

什么是字面量

在计算机科学中,字面量(literal)是用于表达源代码中一个固定值的表示法(notation)。几乎所有计算机编程语言都具有对基本值的字面量表示,诸如:整数、浮点数以及字符串;而有很多也对布尔类型和字符类型的值也支持字面量表示;还有一些甚至对枚举类型的元素以及像数组、记录和对象等复合类型的值也支持字面量表示法。
-----来自 百度百科

下面代码中"String str1 = 'abc' ",'abc'就是字面量,'123'、'123'、'1'、'10'也都是字面量。

 

 

什么是符号引用

符号引用属于编译原理方面的概念,包括了下面三类常量:

  • 类和接口的全限定名
  • 字段的名称和描述符
  • 方法的名称和描述符

上面这种干巴巴的解释,自己看的都是一头雾水,网上找了点资料:https://www.zhihu.com/question/30300585

看下RednaxelaFX是如何解释的(基于RednaxelaFX的内容,又添加了查阅到的其他资料,想了解的看原文)

public class X {
  public void foo() {
    bar();
  }

  public void bar() { }
}

将这个java文件编译成class文件之后,使用javap -v 命令,查看汇编内容。

Classfile /C:/Users/CYX/Desktop/X.class
  Last modified 2018-10-22; size 394 bytes
  MD5 checksum ff677d972ff716aed189e3c7cf453ebc
  Compiled from "X.java"
public class com.server.X
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#16         // java/lang/Object."<init>":()V
   #2 = Methodref          #3.#17         // com/server/X.bar:()V
   #3 = Class              #18            // com/server/X
   #4 = Class              #19            // java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Utf8               Code
   #8 = Utf8               LineNumberTable
   #9 = Utf8               LocalVariableTable
  #10 = Utf8               this
  #11 = Utf8               Lcom/server/X;
  #12 = Utf8               foo
  #13 = Utf8               bar
  #14 = Utf8               SourceFile
  #15 = Utf8               X.java
  #16 = NameAndType        #5:#6          // "<init>":()V
  #17 = NameAndType        #13:#6         // bar:()V
  #18 = Utf8               com/server/X
  #19 = Utf8               java/lang/Object
{
  public com.server.X();
    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   Lcom/server/X;

  public void foo();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokevirtual #2                  // Method bar:()V
         4: return
      LineNumberTable:
        line 6: 0
        line 7: 4
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/server/X;

  public void bar();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 10: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       1     0  this   Lcom/server/X;
}
SourceFile: "X.java"

在上面Class文本中,有一个Constant pool(常量池),里面存储了该Class文件里面的大部分常量的内容。

考察foo()方法里面的一条字节码指令:

1: invokevirtual 		#2             // Method bar:()V

这个字节码指令在代码中对应:bar();,来看下是啥意思

invokevirtual

对普通实例方法调用是在运行时根据对象类型进行分派的,这类方法通过调用invokevirtual指令实现。

条invokevirtual指令都会带有一个表示索引的参数,运行时常量池在该索引的项为某个方法的符号引用

这个符号引用可以提供方法所在对象类型的内部二进制名称、方法名称和方法描述。

"#2"这个参数是Class文件里常量池的下标,那么去Class常量池中找下标为2的常量池项:

#2 = Methodref          #3.#17         // com/server/X.bar:()V

顺着后面的"#3"、"#14",把能传递引用到的常量池项都找出来

(按照深度优先进行排序)

1: invokevirtual 		#2             // Method bar:()V
#2 = Methodref          #3.#17         // com/server/X.bar:()V
#3 = Class              #18            // com/server/X
#18 = Utf8              com/server/X
#17 = NameAndType       #13:#6         // bar:()V
#13 = Utf8              bar
#6 = Utf8               ()V

转换成关系树是这个样子

标记为Utf8的常量池项在Class文件中实际为CONSTANT_Utf8_info,是以略微修改过的UTF-8编码的字符串文本。

由此可以看出,Class文件中的invokevirtual指令的操作数经过几层间接之后,最后都是由字符串来表示的

这就是Class文件里的“符号引用”的实态:带有类型(tag) / 结构(符号间引用层次)的字符串。

由此可见,符号引用通常是设计字符串的——用文本形式来表示引用关系。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值