博客里原创的文章都是我学习时的思考,没有任何指导意义,我会不断学习,不断修改错误。
1 导言
Java虚拟机在内存中有一个专门来存储String实例的常量池,即全局共享的字符串常量池,记为A。在类加载的resolve(解析)阶段,虚拟机在解析对应类的class文件时,发现class文件常量池表项中存在CONSTANT_String_info类型的常量“hello world”,那么就在A处创建并驻留一个String实例来对应” hello world “字面量,只在先前尚未有内容相同的字面量驻留过的前提下才需要创建新的String实例。
2 导言举例
接下来,我们开始看个例子,源代码如下:
public class StringTest {
public void method1(){
String string = new String("hello world");
}
}
javap - verbose StringTest,javap工具可以方便我们分析Class文件。
- 常量池#17就是CONSTANT_String_info类型的常量
- 常量池#17就是CONSTANT_Utf8_info类型的常量
- #17 = String #18 意思为 #17 值为“hello world”
虚拟机在执行method1方法之前,需要创建创建StringTest的实例吧,创建实例,那必须首先加载StringTest才可以。类加载(包括加载、验证、准备、解析)的解析过程中发现了#17这个字符串常量,发现虚拟机常量池还没有字面量为“hello world”的实例,创建实例并加入到常量池。
3 String string = new String(“hello world”)?
那么String string = new String(“hello world”);该代码段到底创建了几个对象呢?
只有运行时的类加载过程与实际执行某个代码片段,两者必须分开讨论才有那么点意义。
运行时的类加载过程
为了执行问题中的代码片段,其所在的类必然要先被加载,而且同一个类最多只会被加载一次。在类加载的过程中创建并驻留(A中)一个String实例作为常量来对应”hello world”字面量。 而且只在先前尚未有内容相同的字符串驻留过的前提下才需要创建新的String实例。(也就说可能是一个,也可能是0个,这要看代码上下而定)。
实际执行某个代码片段
String string = new String("hello world");
对应的字节码文件如下:
1 new 字节码指令介绍
new is used to create object instances.
new takes a single parameter, <class>, the name of the class of object you want to create. <class> is resolved into a Java class (see Chapter 7 for a discussion of how classes are resolved).
Then new determines the size in bytes of instances of the given class and allocates memory for the new instance from the garbage collected heap. The fields of the instance are set to the initial value 0 (for numeric and boolean fields), or null (for reference fields). Next, a reference to the new object is pushed onto the operand stack.
Note that the new object is initialize uninitialized - before the new object can be used, one of its <init> methods must be called using invokespecial, as shown in the example below.
new字节码指令的意思是为对象(new后面的参数即为对象类型,这里是java.lang.String)申请内从空间,然后初始化(数字和boolean给0,引用给null),然后把这个内存空间的引用推到栈顶。注意,在使用这个对象之前必须使用 invokespecial 字节码指令调用对象的方法。
现在栈顶是一个内存空间的引用。
2 dup 字节码指令介绍
This pops the top single-word value off the operand stack, and then pushes that value twice - i.e. it makes an extra copy of the top item on the stack.
dup把栈顶元素复制一份放在栈顶
现在栈顶和次栈顶指向同一个内存空间
3 ldc 字节码指令介绍
ldc pushes a one-word constant onto the operand stack. ldc takes a single parameter, <value>, which is the value to push. The following Java types can be pushed using ldc:
int
float
String
Pushing a String causes a reference to a java.lang.String object to be constructed and pushed onto the operand stack. Pusing an int or a float causes a primitive value to be pushed onto the stack.
Notes
1. Where possible, its more efficient to use one of bipush, sipush, or one of the const instructions instead of ldc.
2. If the same string constant (i.e. a string with the same sequence of characters) appears in several different class files, only one String instance is built for that constant. The String.intern() method can be used to retrieve the instance used for a given sequence of characters.
这个我每太看明白,此处我引用别人的话来解释,最后我会给出引用博文的地址。
ldc指令只是把先前在类加载过程中已经创建好的一个String对象(”hello world”)的一个引用压到操作数栈顶而已,并不新创建String对象。
现在操作数栈的内容为
4 invokespecial 字节码指令介绍
invokespecial is used in certain special cases to invoke a method Specifically, invokespecial is used to invoke:
the instance initialization method, <init>
a private method of this
a method in a superclass of this
The main use of invokespecial is to invoke an object's instance initialization method, <init>, during the construction phase for a new object
下图是invokespecial指令执行前后操作数栈的变化情况
而我们的现在操作数栈的情况如下
将S3作为参数调用String的< ini t>方法完成S2所指对象的初始化工作,调用完成后,操作数栈的情况如下
5 astore_1字节码指令介绍
Pops objectref (a reference to an object or array) off the stack and stores it in local variable <n>, where <n> is 0, 1, 2 or 3. <n> must be a valid local variable number in the current frame.
引用出栈并保存至本地变量表的第二个位置即局部变量string,第一个位置留给this变量。
到此代码执行完毕,实际上只是在堆创建了一个对象。