本章,我将采用一个例子来讲解class文件结构,并介绍jvm是如何操作分配内存的。
我们新建一个类,设置了两个比较简单的例子,就是创建String的,因为这个类对象处理起来有点特殊,所以拿过来作为例子来讲解
public class StringKnowledgeTest { public String getName(){ String fengfuName="fengfu"; return fengfuName; } public String getnewName(){ String zhaoff=new String("zhaoff");//这语句,一共生成了几个对象 return zhaoff; } }
使用javap -verbose StringKnowledgeTest 查看类结构
常量池为:
const #1 = class #2; // knowledgeTest/StringKnowledgeTest const #2 = Asciz knowledgeTest/StringKnowledgeTest; const #3 = class #4; // java/lang/Object const #4 = Asciz java/lang/Object; const #5 = Asciz ; const #6 = Asciz ()V; const #7 = Asciz Code; const #8 = Method #3.#9; // java/lang/Object."":()V const #9 = NameAndType #5:#6;// "":()V const #10 = Asciz LineNumberTable; const #11 = Asciz LocalVariableTable; const #12 = Asciz this; const #13 = Asciz LknowledgeTest/StringKnowledgeTest;; const #14 = Asciz getName; const #15 = Asciz ()Ljava/lang/String;; const #16 = String #17; // fengfu const #17 = Asciz fengfu; const #18 = Asciz fengfuName; const #19 = Asciz Ljava/lang/String;; const #20 = Asciz getnewName; const #21 = class #22; // java/lang/String const #22 = Asciz java/lang/String; const #23 = String #24; // zhaoff const #24 = Asciz zhaoff; const #25 = Method #21.#26; // java/lang/String."":(Ljava/lang/String;)V const #26 = NameAndType #5:#27;// "":(Ljava/lang/String;)V const #27 = Asciz (Ljava/lang/String;)V; const #28 = Asciz SourceFile; const #29 = Asciz StringKnowledgeTest.java;
从常量池中第17个来看,方法getName中的String "fengfu"在经过编译后,就会被保存到常量池中,方法getnewName也是这样,"zhaoff"经过编译后,会放入class常量池中,并最终内存方法区中。
getName对应的jvm指令为:
public java.lang.String getName(); Code: Stack=1, Locals=2, Args_size=1 0: ldc #16; //String fengfu //该操作为:从运行时常量池中提取数据推入操作数栈,reference类型则为推入reference类型 2: astore_1 //该操作为:将一个reference类型数据保存到局部变量表 3: aload_1 //该操作为:从局部变量表中读取一个reference类型到操作数栈 4: areturn //从操作数栈返回一个reference数据,结束方法 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this LknowledgeTest/StringKnowledgeTest; 3 2 1 fengfuName Ljava/lang/String;
jvm指令的整个过程,都是操作局部变量表与操作数栈,通过出入栈方式来完成该方法。
本过程一个只生成了一个字符串常量,并且在编译后,就保存在了class文件常量池中。
执行过程中局部变量表和操作数栈的变化如下:
getnewName方法对应的jvm指令如下:
public java.lang.String getnewName(); Code: Stack=3, Locals=2, Args_size=1 0: new #21; //class java/lang/String //new 一个 String 并将该String的reference压入操作数栈 3: dup //复制操作数栈栈顶的值,并压入操作数栈 4: ldc #23; //String zhaoff //该操作为:从运行时常量池中提取数据推入操作数栈,reference类型则为推入reference类型 6: invokespecial #25; //Method java/lang/String."":(Ljava/lang/String;)V //调用String 构造函数 9: astore_1 //该操作为:将一个reference类型数据保存到局部变量表 10: aload_1 //该操作为:从局部变量表中读取一个reference类型到操作数栈 11: areturn //从操作数栈返回一个reference数据,结束方法 LocalVariableTable: Start Length Slot Name Signature 0 12 0 this LknowledgeTest/StringKnowledgeTest; 10 2 1 zhaoff Ljava/lang/String;
执行过程中局部变量表和操作数栈的变化如下:
总结,从上面可以看出来,new String("abc") 产生了两个对象,要比String test="ABC"jvm指令要复杂,而且产生的对象也要多一个。第二种方式虽然产生了一个对象,但该对象位于常量池中,后期再次使用到"ABC"时,会服用常量池中的该变量。