在理解常量值之前我们先了解几个基本概念。
1.字面量:可以理解为实际值,int a = 8中的8和String a = "hello"中的hello都是字面量。
2.符号引用:符号引用就是一个字符串。解析后就成了能直接定位到这个字符串要表示的内容的指针了。可参考JVM里的符号引用如何存储?。可以利用javap -verbose ByteCode.class
看到上一篇文章的效果。
3.直接引用:是JVM(或其它运行时环境)所能直接使用的形式。
符号引用主要包括三种常量:
1.类和接口的全限定名
2.字段的名称和描述符
3.方法的名称和描述符
直接引用可以是:
1.直接指向目标的指针。(个人理解为:指向对象,类变量和类方法的指针)
2.相对偏移量。 (指向实例的变量,方法的指针)
3.一个间接定位到对象的句柄。
1.什么是常量池?
常量池的本质是缓存。不同的类共用一个运行时常量池。
常量池分为:
- Class 文件常量池(非运行时常量池,本地文件)
- 运行时常量池(方法区内存中,元空间)
- 字符串常量池(堆内存)
1.1 Class 文件常量池(静态常量池)
Class常量池中存放的编译期生成的各种字面量和符号引用。这部分内容将在类加载后进入方法区的运行时常量池中存放。
接口A源码:
package com.xiaoer;
public interface A {
default void defaultB() {
System.out.println("defaultB");
}
static void staticA() {
System.out.println("staticA");
}
}
执行javap -verbose ByteCode.class
查看class文件结构。
Classfile /E:/work_space/04 study/test01/target/classes/com/xiaoer/A.class
Last modified 2019-9-19; size 448 bytes
MD5 checksum 2dd9130ca78cd6781198832576a02832
Compiled from "A.java"
public interface com.xiaoer.A
minor version: 0
major version: 54
flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
Constant pool:
#1 = Fieldref #17.#18 // java/lang/System.out:Ljava/io/PrintStream;
#2 = String #7 // defaultB
#3 = Methodref #19.#20 // java/io/PrintStream.println:(Ljava/lang/String;)V
#4 = String #14 // staticA
#5 = Class #21 // com/xiaoer/A
#6 = Class #22 // java/lang/Object
#7 = Utf8 defaultB
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lcom/xiaoer/A;
#14 = Utf8 staticA
#15 = Utf8 SourceFile
#16 = Utf8 A.java
#17 = Class #23 // java/lang/System
#18 = NameAndType #24:#25 // out:Ljava/io/PrintStream;
#19 = Class #26 // java/io/PrintStream
#20 = NameAndType #27:#28 // println:(Ljava/lang/String;)V
#21 = Utf8 com/xiaoer/A
#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 void defaultB();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #1 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #2 // String defaultB
5: invokevirtual #3 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 5: 0
line 6: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this Lcom/xiaoer/A;
public static void staticA();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=0, args_size=0
0: getstatic #1 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #4 // String staticA
5: invokevirtual #3 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 8: 0
line 9: 8
}
1.2 运行时常量池
静态常量池是针对每个被加载进入内存的class文件解析后,存放各个字面量值,符号引用的数据,而运行时常量区就是把所有的静态常量的数据汇总到一起(模糊来说)
1.3 字符串常量池
JDK1.7之前运行时常量池逻辑包含字符串常量池存放在方法区。
DK1.7 以后字符串常量池被从方法区拿到了堆中,运行时常量池仍在方法区。
2.为什么需要常量池?
常量池其实就是跟数据库连接池的目的都是一样的。节省资源,提高效率。
3.String s1 = “Hello”,到底有没有在堆中创建对象?常量池存放的到底是对象还是对象引用?
是有的,所有创建的对象都在堆上。只是将字符串的引用放进字符串常量池。
参考资料:
1.Java的字面量和符号引用
2.Java系列20-字面量
3.走进java_符号引用与直接引用
4.JVM里的符号引用如何存储?
5.彻底弄懂java中的常量池
6.在Java虚拟机中,字符串常量到底存放在哪?
上面的资料都是从不明白到搞懂过程中筛选的,建议可以认真看看,串联起来就容易懂了。