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

来源:互联网 发布:ubuntu root无声音 编辑:程序博客网 时间:2024/06/14 02:57

最近在看《深入理解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 -versionjavac 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>":()Vconst #2 = class        #22;    //  java/lang/StringBuilderconst #3 = Method       #2.#21; //  java/lang/StringBuilder."<init>":()Vconst #4 = class        #23;    //  java/lang/Stringconst #5 = String       #24;    //  12const #6 = Method       #4.#25; //  java/lang/String."<init>":(Ljava/lang/String;)Vconst #7 = Method       #2.#26; //  java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;const #8 = String       #27;    //  3const #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;    //  Testconst #12 = class       #31;    //  java/lang/Objectconst #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>":()Vconst #22 = Asciz       java/lang/StringBuilder;const #23 = Asciz       java/lang/String;const #24 = Asciz       12;const #25 = NameAndType #13:#32;//  "<init>":(Ljava/lang/String;)Vconst #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: 0public 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字节码的基本知识,并逐句分析字节码指令以及栈深度。

0 0
原创粉丝点击