学习JVM时候经常遇到这三个名词:常量池、运行时常量池、字符串常量池,接下来了解一下这三个到底有什么区别,也便于自己复习用到。
常量池
package com.example.demo;
/**
* @author zinsanity
* @date 2020-11-21 16:06
* @desc
*/
public class Review {
public static void main(String[] args) {
String info = "hello world";
int a = 666;
final int b = 66;
int c = 36728;
}
}
复制代码
此代码编译以后,使用javap -v Review.class命令查看字节码
Classfile /D:/idea_projects/demo/target/classes/com/example/demo/Review.class
Last modified 2020-11-21; size 561 bytes
MD5 checksum 09914fecad4a776e7ac86d1a6fd43baa
Compiled from "Review.java"
public class com.example.demo.Review
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #5.#26 // java/lang/Object."<init>":()V
#2 = String #27 // hello world
#3 = Integer 36728
#4 = Class #28 // com/example/demo/Review
#5 = Class #29 // java/lang/Object
#6 = Utf8 <init>
#7 = Utf8 ()V
#8 = Utf8 Code
#9 = Utf8 LineNumberTable
#10 = Utf8 LocalVariableTable
#11 = Utf8 this
#12 = Utf8 Lcom/example/demo/Review;
#13 = Utf8 main
#14 = Utf8 ([Ljava/lang/String;)V
#15 = Utf8 args
#16 = Utf8 [Ljava/lang/String;
#17 = Utf8 info
#18 = Utf8 Ljava/lang/String;
#19 = Utf8 a
#20 = Utf8 I
#21 = Utf8 b
#22 = Utf8 c
#23 = Utf8 MethodParameters
#24 = Utf8 SourceFile
#25 = Utf8 Review.java
#26 = NameAndType #6:#7 // "<init>":()V
#27 = Utf8 hello world
#28 = Utf8 com/example/demo/Review
#29 = Utf8 java/lang/Object
{
public com.example.demo.Review();
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 8: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/example/demo/Review;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=5, args_size=1
0: ldc #2 // String hello world
2: astore_1
3: sipush 666
6: istore_2
7: bipush 66
9: istore_3
10: ldc #3 // int 36728
12: istore 4
14: return
LineNumberTable:
line 10: 0
line 11: 3
line 12: 7
line 13: 10
line 14: 14
LocalVariableTable:
Start Length Slot Name Signature
0 15 0 args [Ljava/lang/String;
3 12 1 info Ljava/lang/String;
7 8 2 a I
10 5 3 b I
14 1 4 c I
MethodParameters:
Name Flags
args
}
SourceFile: "Review.java"
复制代码
从字节码文件中可以看到Constant Pool。也就是说,每个class文件,都有一个常量池。先来看这段字节码,这就是我们的main方法编译后的字节码,其中0: ldc #2 意思是从常量池中取出第2个位置的数据存入到astore_1当中。那么下面去常量池中看一下
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=5, args_size=1
0: ldc #2 // String hello world
2: astore_1
3: sipush 666
6: istore_2
7: bipush 66
9: istore_3
10: ldc #3 // int 36728
12: istore 4
14: return
复制代码
在字节码文件中,如下片段即表示常量池。#2位置对应的是String,然后还要去找#27位置,#27位置对应的是hello word。所以,0: ldc #2最终从常量池中取出来一个String类型的hello word字符串,存入到astore_1中。然后sipush就是将666存入到istore_2位置,bipush将66存入到istore_3位置,ldc命令又会去常量池找#3位置的数据存入。
Constant pool:
#1 = Methodref #5.#26 // java/lang/Object."<init>":()V
......
#29
复制代码
第一个疑问:为什么都是int类型的数据,用的指令却不同?按照JVM的规范,根据int值范围采用不同的指令将int数值入栈。
第二个疑问:为什么666和66没有被放到常量池中?据了解,对于int类型,只有超过一定范围的int值,才会放到常量池中,
这也就解释了36728为何被放到了常量池中。
复制代码
运行时常量池
在常量池中,可以看到都是用#1 #2 #3这些临时符号来表示。当运行某个程序时候,JVM会把所有的字节码文件加入到内存当中,在经过链接、验证后,将#1 #2 #3这些符号全部转换成内存中的实际地址,放入到运行时常量池运行。运行时常量池是放在方法区中的,全局只有一份,是一个被所有的class共享的区域。
字符串常量池
比如说
String a = "test1";
String b = "test2";
复制代码
这两个字符串"test1"和"test2"在编译完成后,首先存放在常量池中。在程序运行时,加载进入内存以后,字符串就会加载进入到字符串常量池中。jdk1.7以后,字符串常量池位于堆中。
近期评论