背景
在学习 《阿里巴巴Java开发手册》详解/24 代码调试的正确姿势时,无意间发现了一个在秋招面试经常会被问的问题 :
当你通过new String(“test”)时,到底会创建几个对象,是否会放入常量池?
答案
- 在执行这行代码前,没有字面量test字符串出现,则会创建两个实例对象, 一个在堆中,一个在常量池中
- 否则只会在堆中创建一个实例对象
分析
代码.V1:
public class App
{
public static void main( String[] args )
{
String a = new String("testString");
System.out.println(a);
}
}
将代码反汇编得到:
0 new #2 <java/lang/String> //创建String对象
3 dup //将刚栈顶的reference复制压栈
4 ldc #3 <testString> //将常量池中的“testString”对象的reference压栈
6 invokespecial #4 <java/lang/String.<init>> //初始化String对象
9 astore_1
10 getstatic #5 <java/lang/System.out>
13 aload_1
14 invokevirtual #6 <java/io/PrintStream.println>
17 return
指令解释:
- new指令完成了实例对象的内存分配,初始化零值,对象头设置等操作,从jvm视角完成了一个对象的创建操作,并将reference压栈
- dup指令将栈顶数值复制并压栈。jvm这样的做的目的是为了效率考虑,减少一次aload操作,用dup来代替。
- ldc是将常量池中的常量值推入栈顶,也就是将testString对象的reference值压栈
- invokespecial指令才是从java代码的角度去完成对象的创建,将对象按我们的意愿去初始化
代码.V1总结:
代码中的字面量都会在编译期间被放入.class文件的静态常量池中。当执行 new String(“testString”)时,会加载运行时常量池中的对象引用值,然后通过<init>方法来完成初始化,得到新的对象。
代码.V2
public class App
{
public static void main( String[] args )
{
String a = "testString";
System.out.println(a);
}
}
反汇编
0 ldc #2 <testString>
2 astore_1
3 getstatic #3 <java/lang/System.out>
6 aload_1
7 invokevirtual #4 <java/io/PrintStream.println>
10 return
代码.V2总结
具体指令就不解释了,可以对比V1的反汇编指令。
可以看出V2中直接就是加载了常量池中的testString对象的引用,然后存储到本地变量a中,本质上是没有创建新的String对象,只是创建了一个本地引用变量来接收实例对象的引用值