您的位置:首页 > 其它

由常量池 运行时常量池 String intern方法想到的(一)

2016-03-14 19:41 429 查看
最近在看《深入理解java虚拟机》,看到了常量池(Constant Pool Table)和运行时常量池(Runtime Constant Pool)这两个概念,对这两个概念不是很理解。又看到了String类的intern方法,intern方法也没用过。于是,查了查,记录如下。

以后遇到了问题之后,写blog时,先下问题的结论。

结论

常量池这个概念是针对java class文件而言的。当java代码被编译成字节码(class)文件时,会将字面量(文本字符串,声明为final的常量值)、符号引用存放在class文件的常量池中。

运行时常量池是JVM内存中方法区的一部分。当class文件被加载(load)到JVM时会将常量池中的内容存放在运行时常量池(在perm区中,即永久代)中。上面 这一句只是针对JDK1.6及之前的版本适用。JDK1.7之后对方法区进行了一些优化。1.7之后将运行时常量池移到了堆中。原因,Perm 区是一个类静态的区域,主要存储一些加载类的信息,常量池,方法片段等内容,默认大小只有4M。一旦超过范围,会直接产生java.lang.OutOfMemoryError: PermGen space错误。

intern方法:当调用该方法时,会在运行时常量池查看有无该字符串,如果有的话,直接返回该引用(这个字符串在运行时常量池中的地址);如果没有的话,在运行时常量池中存放一份数据,并返回该字符串在运行时常量池中的地址。这都是在JDK1.6及之前发生的事情。如果是1.7之后,如果发现在运行时常量池中没有的话,在运行时常量池中存放的是一份这个字符串String的引用(即这个String对象在堆中的地址),并返回该引用的值。

终极结论

终极结论针对JDK1.7及以后。

常量池就是class文件中的字面量及符号引用(符号引用是指被java编译器编译之后出现的各种类[之所以说是java编译器编译之后出现的各种类,而不是java源代码中出现的类是因为,java编译器会对java源代码进行一些优化,如String的”+”操作]及使用到的方法名,参数和返回值)。

运行时常量池就是class文件被加载(load)到JVM之后,常量池存放的内存区,该内存区属于Heap区。

inter方法:当string对象s调用intern时(
s.intern()
),如果运行时常量池中有s的值,则直接返回该字符串在运行时常量池的地址;如果不存在s的值,则将s的地址(引用)存放在运行时常量池,并返回s的地址(引用)。

下面对“如果不存在s的值,则将s的地址(引用)存放在运行时常量池,并返回s的地址(引用)。”举例图解一下:

public class Test {
public static void main(String[] args) {
String s = new String("12") + new String("3");
s.intern();
}
}


这两句话是main方法进来之后就执行的。

String s = new String("12") + new String("3");


这句话会被编译器优化(可以从字节码指令中看出)。可以通过下面的命令查看上面的代码对应的字节码指令:

//查看java版本
java -version
javac Test.java     //编译
//-verbose查看class文件中的常量池
//-c 表示将生成JVM字节码指令
javap -verbose -c Test


本文所使用的java版本如下:

java version "1.6.0_45"
Java(TM) SE Runtime Environment (build 1.6.0_45-b06)
Java HotSpot(TM) 64-Bit Server VM (build 20.45-b01, mixed mode)


上面命令的完整输出如下所示:

Compiled from "Test.java"
public class Test extends java.lang.Object
SourceFile: "Test.java"
minor version: 0
major version: 50
Constant pool:
const #1 = Method       #12.#21;        //  java/lang/Object."<init>":()V
const #2 = class        #22;    //  java/lang/StringBuilder
const #3 = Method       #2.#21; //  java/lang/StringBuilder."<init>":()V
const #4 = class        #23;    //  java/lang/String
const #5 = String       #24;    //  12
const #6 = Method       #4.#25; //  java/lang/String."<init>":(Ljava/lang/String;)V
const #7 = Method       #2.#26; //  java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
const #8 = String       #27;    //  3
const #9 = Method       #2.#28; //  java/lang/StringBuilder.toString:()Ljava/lang/String;
const #10 = Method      #4.#29; //  java/lang/String.intern:()Ljava/lang/String;
const #11 = class       #30;    //  Test
const #12 = class       #31;    //  java/lang/Object
const #13 = Asciz       <init>;
const #14 = Asciz       ()V;
const #15 = Asciz       Code;
const #16 = Asciz       LineNumberTable;
const #17 = Asciz       main;
const #18 = Asciz       ([Ljava/lang/String;)V;
const #19 = Asciz       SourceFile;
const #20 = Asciz       Test.java;
const #21 = NameAndType #13:#14;//  "<init>":()V
const #22 = Asciz       java/lang/StringBuilder;
const #23 = Asciz       java/lang/String;
const #24 = Asciz       12;
const #25 = NameAndType #13:#32;//  "<init>":(Ljava/lang/String;)V
const #26 = NameAndType #33:#34;//  append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
const #27 = Asciz       3;
const #28 = NameAndType #35:#36;//  toString:()Ljava/lang/String;
const #29 = NameAndType #37:#36;//  intern:()Ljava/lang/String;
const #30 = Asciz       Test;
const #31 = Asciz       java/lang/Object;
const #32 = Asciz       (Ljava/lang/String;)V;
const #33 = Asciz       append;
const #34 = Asciz       (Ljava/lang/String;)Ljava/lang/StringBuilder;;
const #35 = Asciz       toString;
const #36 = Asciz       ()Ljava/lang/String;;
const #37 = Asciz       intern;

{
public Test();
Code:
Stack=1, Locals=1, Args_size=1
0:   aload_0
1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
4:   return
LineNumberTable:
line 1: 0

public static void main(java.lang.String[]);
Code:
Stack=4, Locals=2, Args_size=1
0:   new     #2; //class java/lang/StringBuilder
3:   dup
4:   invokespecial   #3; //Method java/lang/StringBuilder."<init>":()V
7:   new     #4; //class java/lang/String
10:  dup
11:  ldc     #5; //String 12
13:  invokespecial   #6; //Method java/lang/String."<init>":(Ljava/lang/String;)V
16:  invokevirtual   #7; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19:  new     #4; //class java/lang/String
22:  dup
23:  ldc     #8; //String 3
25:  invokespecial   #6; //Method java/lang/String."<init>":(Ljava/lang/String;)V
28:  invokevirtual   #7; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
31:  invokevirtual   #9; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
34:  astore_1
35:  aload_1
36:  invokevirtual   #10; //Method java/lang/String.intern:()Ljava/lang/String;
39:  pop
40:  return
LineNumberTable:
line 3: 0
line 4: 35
line 5: 40

}


从上面可以看到主版本号、次版本号,常量池,java编译器自动添加的默认构造函数的字节码,main方法的字节码。

结束语

这篇博文引出了问题,何为常量池,何为运行时常量池,String#intern()方法是干什么的;举了一个例子,引出了java字节码指令。下篇文章由常量池 运行时常量池 String intern方法想到的(二),学习下java字节码的基本知识,并逐句分析字节码指令以及栈深度。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: