String string = new String("hello world")?

博客里原创的文章都是我学习时的思考,没有任何指导意义,我会不断学习,不断修改错误。

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文件。

这里写图片描述

  1. 常量池#17就是CONSTANT_String_info类型的常量
  2. 常量池#17就是CONSTANT_Utf8_info类型的常量
  3. #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变量。

到此代码执行完毕,实际上只是在堆创建了一个对象。


4 分析还未结束,我会不断修改文中不妥与错误的地方

参考地址: http://www.iteye.com/topic/774673

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
String a="hello world"; //在java中有一个常量池,当创建String 类型的引用变量给它赋值时,java会到它的常量池中找"hello world"是不是在常量池中已存在。如果已经存在则返回这个常量池中的"hello world"的地址(在java中叫引用)给变量a 。注意a并不是一个对象,而是一个引用类型的变量。它里面存的实际上是一个地址值,而这个值是指向一个字符串对象的。在程序中凡是以"hello world"这种常量似的形式给出的都被放在常量池中。 String b=new String("hello world"); //这种用new关键字定义的字符串,是在堆中分配空间的。而分配空间就是由new去完成的,由new去决定分配多大空间,并对空间初始化为字符串"hello world" 返回其在堆上的地址。 通过上面的原理,可以做如下实验: String a="hello world"; String b="hello world"; String c=new String("hello world"); String d=new String("hello world"); if(a==b) System.out.println("a==b"); else System.out.println("a!=b"); if(c==d) System.out.println("c==d"); else System.out.println("c!=d"); //输出结果: a==b c!=d 为什么会出现上面的情况呢? String a="hello world"; String b="hello world"; 通过上面的讲解可以知道,a和b都是指向常量池的同一个常量字符串"hello world"的,因此它们返回的地址是相同的。a和b都是引用类型,相当于c语言里面的指针。java里面没有指针的概念,但是实际上引用变量里面放的确实是地址值,只是java为了安全不允许我们对想c语言中的那样对指针进行操作(如++ 、--)等。这样就有效的防止了指针在内存中的游离。 而对于 String c=new String("hello world"); String d=new String("hello world"); 来说是不相等的,他们是有new在堆中开辟了两块内存空间,返回的地址当然是不相等的了。如果我们要比较这两个字符串的内容怎么办呢?可以用下面的语句: if(c.equals(d)) System.out.println("c==d"); else System.out.println("c!=d"); //输出 c==d
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值