Class 文件常量池
class 文件是以字节为单位的二进制数据流,java 编译器将 java 源码文件编译成 .class
字节码文件寄存在磁盘上,.class
中就蕴含文件常量池 (非运行时常量池), 在编译期间就确定了,.class
文件遵循 jvm 虚拟机标准.
Java 源码:
package com.rumenz;
public class rumenz{
public Integer id=10;
public String name="入门";
public final int age=100;
public void setId(Integer id){this.id=id;}
}
查看字节码
> javac rumenz.java
> javap -v rumenz
正告: 二进制文件 rumenz 蕴含 com.rumenz.rumenz
Classfile /code/rumenz.class
Last modified 2020-10-17; size 542 bytes
MD5 checksum 6a8a73fb6327c1a64e9ad54e53e94afd
Compiled from "rumenz.java"
public class com.rumenz.rumenz
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #9.#24 // java/lang/Object."<init>":()V
#2 = Fieldref #8.#25 // com/rumenz/rumenz.id:I
#3 = String #26 // 入门
#4 = Fieldref #8.#27 // com/rumenz/rumenz.name:Ljava/lang/String;
#5 = Methodref #28.#29 // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
#6 = Fieldref #8.#30 // com/rumenz/rumenz.age:Ljava/lang/Integer;
#7 = Methodref #28.#31 // java/lang/Integer.intValue:()I
#8 = Class #32 // com/rumenz/rumenz
#9 = Class #33 // java/lang/Object
#10 = Utf8 id
#11 = Utf8 I
#12 = Utf8 name
#13 = Utf8 Ljava/lang/String;
#14 = Utf8 age
#15 = Utf8 Ljava/lang/Integer;
#16 = Utf8 <init>
#17 = Utf8 ()V
#18 = Utf8 Code
#19 = Utf8 LineNumberTable
#20 = Utf8 setId
#21 = Utf8 (Ljava/lang/Integer;)V
#22 = Utf8 SourceFile
#23 = Utf8 rumenz.java
#24 = NameAndType #16:#17 // "<init>":()V
#25 = NameAndType #10:#11 // id:I
#26 = Utf8 入门
#27 = NameAndType #12:#13 // name:Ljava/lang/String;
#28 = Class #34 // java/lang/Integer
#29 = NameAndType #35:#36 // valueOf:(I)Ljava/lang/Integer;
#30 = NameAndType #14:#15 // age:Ljava/lang/Integer;
#31 = NameAndType #37:#38 // intValue:()I
#32 = Utf8 com/rumenz/rumenz
#33 = Utf8 java/lang/Object
#34 = Utf8 java/lang/Integer
#35 = Utf8 valueOf
#36 = Utf8 (I)Ljava/lang/Integer;
#37 = Utf8 intValue
#38 = Utf8 ()I
{
public int id;
descriptor: I
flags: ACC_PUBLIC
public java.lang.String name;
descriptor: Ljava/lang/String;
flags: ACC_PUBLIC
public final java.lang.Integer age;
descriptor: Ljava/lang/Integer;
flags: ACC_PUBLIC, ACC_FINAL
public com.rumenz.rumenz();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: bipush 10
7: putfield #2 // Field id:I
10: aload_0
11: ldc #3 // String 入门
13: putfield #4 // Field name:Ljava/lang/String;
16: aload_0
17: bipush 100
19: invokestatic #5 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
22: putfield #6 // Field age:Ljava/lang/Integer;
25: return
LineNumberTable:
line 11: 0
line 12: 4
line 13: 10
line 14: 16
public void setId(java.lang.Integer);
descriptor: (Ljava/lang/Integer;)V
flags: ACC_PUBLIC
Code:
stack=3, locals=3, args_size=2
0: aload_0
1: iconst_3
2: aload_1
3: invokevirtual #7 // Method java/lang/Integer.intValue:()I
6: iadd
7: putfield #2 // Field id:I
10: return
LineNumberTable:
line 17: 0
line 18: 10
}
SourceFile: "rumenz.java"
什么是常量
文本字面量
#29 = Utf8 入门
final 润饰成员变量(动态变量, 实例变量, 局部变量)
对于根本类型
public Integer id=10;
常量池中只保留了他的字段描述符 I 和字段名称 value, 他们的字面量不会存在于常量池。
符号援用
符号援用次要设波及编译原理方面的概念,包含上面三类常量:
- 类和接口的全限定名, 也就是 java/lang/String, 将原来的
.
替换成/
, 次要用于在运行时解析失去类的间接援用。
#8 = Class #32 // com/rumenz/rumenz
- 字段的名称和描述符: 就是类或者接口中申明的变量, 包含类级别的变量和实例级的变量
#14 = Utf8 age
- 办法中的名称和描述符: 参数类型 + 返回值
#20 = Utf8 setId
运行时常量池
运行时常量池是办法区的一部分,所以也是全局共享的,JVM 在执行某个类的时候会通过
加载
,链接 (验证, 筹备, 解析)
,初始化
,在加载的时候须要做:
- 通过一个类的全类限定名获取此类的二进制字节流。
- 在堆内存生成一个 java.lang.Class 对象, 代表加载这个类, 做为这个类的入口。
一般对象
和类对象
的区别: 一般对象是通过 new 创立进去的。类对象是 JVM 创立的单例对象。
- 将字节流的动态存储构造转化成办法区的运行时数据结构
class 文件常量池进入运行时常量池,所有类独特应用一个运行时常量池,在进入运行时常量的过程中, 多个 class 常量池中雷同的字符串,只会在运行时常量池存在一份,这是一种优化。
运行时常量池的作用是存储 class 文件常量池中的符号援用,同时运行时常量池保留着 class 文件中形容的符号援用, 在类的解析阶段会把这些符号援用转换成间接援用(实例对象的内存地址), 翻译进去的间接援用也是存储在运行时常量池中。class 文件常量池的大部分数据会被加载到运行时常量池。
运行时常量池相比于 class 文件常量池具备动态性,运行时常量池的内容不全副来自于 class 文件常量池, 能够通过代码生成的形式退出到外面。如 String.intern。
String.intern()的用法
拿 String 的内容去 Stringtable 查找, 则返回援用。如果不存在就把该对象的援用存在 Stringtable 中。
字符串常量池
1. 字符串创立的两种形式
- String rumenz1=” 入门 ”;
- String rumenz2=new String(“ 小站 ”);
入门
在编译期间就曾经确定, 会进入字符串常量池, 然而字符串常量池只会保留一个援用, 最终还是会在堆上创立一个 入门
对象。new String 这种形式调用了 String 类的构造函数,new 是创立一个对象实例并初始化该实例, 因而这个字符串对象是在运行时能力确定的, 创立的实例在堆上。
2. 字符串常量的实质
字符串常量池是 JVM 保护的一个字符串实例援用表, 在 HotSpot VM 中它是叫做一个 StringTable 的全局表。在字符串常量池中保护的是字符串实例的援用, 底层 C ++ 保护的就是一个 Hashtable。这些被保护援用的字符串实例, 被称作 被驻留字符串
或interned string
或 进入字符串常量池的字符串
。