java 字符串内存分配的分析与总结(推荐)
经常在网上各大版块都能看到对于java字符串运行时内存分配的探讨,形如:Stringa="123",Stringb=newString("123"),这两种形式的字符串是存放在什么地方的呢,其实这两种形式的字符串字面值"123"本身在运行时既不是存放在栈上,也不是存放在堆上,他们是存放在方法区中的某个常量区,并且对于相同的字符串字面值在内存中只保留一份。下面我们将以实例来分析。
1.==运算符作用在两个字符串引用比较的两个案例:
publicclassStringTest{ publicstaticvoidmain(String[]args){ //part1 Strings1="ilovechina"; Strings2="ilovechina"; System.out.println("result:"+s1==s2);//程序运行结果为true //part2 Strings3=newString("ilovechina"); Strings4=newString("ilovechina"); System.out.println("result:"+s3==s4);//程序运行结果为false } }
我们知道java中==运算符比较的是变量的值,对于引用类型对应的变量的值存放的是引用对象的地址,在这里String是引用类型,这里面的四个变量的值存放的其实是指向字符串的地址。对于part2的执行结果是显然的,因为new操作符会使jvm在运行时在堆中创建新的对象,两个不同的对象的地址是不同的。但是由part1的执行结果,可以看出s1和s2是指向的同一个地址,那么由变量s1,s2指向的字符串是存放在什么地方的呢,jvm又是对字符串如何处理的呢。同样的对于变量s3,s4所指向的堆中的不同的字符串对象,他们会分别在自己的对象空间中保存一份"ilovechina"字符串吗,为了了解jvm是如何处理字符串,首先我们看java编译器生成的字节码指令。通过字节码指令我们来分析jvm将会执行哪些操作。
2.以下为程序生成的部分字节码信息。红色标注的是我们需要关注的部分。
Constantpool: #1=Class#2//StringTest #2=Utf8StringTest #3=Class#4//java/lang/Object #4=Utf8java/lang/Object #5=Utf8<init> #6=Utf8()V #7=Utf8Code #8=Methodref#3.#9//java/lang/Object."<init>":()V #9=NameAndType#5:#6//"<init>":()V #10=Utf8LineNumberTable #11=Utf8LocalVariableTable #12=Utf8this #13=Utf8LStringTest; #14=Utf8main #15=Utf8([Ljava/lang/String;)V #16=String#17//ilovechina字符串地址的引用 #17=Utf8ilovechina #18=Fieldref#19.#21//java/lang/System.out:Ljava/io/PrintStream; #19=Class#20//java/lang/System #20=Utf8java/lang/System #21=NameAndType#22:#23//out:Ljava/io/PrintStream; #22=Utf8out #23=Utf8Ljava/io/PrintStream; #24=Class#25//java/lang/StringBuilder #25=Utf8java/lang/StringBuilder #26=String#27//result: #27=Utf8result: #28=Methodref#24.#29//java/lang/StringBuilder."<init>":(Ljava/lang/String;)V #29=NameAndType#5:#30//"<init>":(Ljava/lang/String;)V #30=Utf8(Ljava/lang/String;)V #31=Methodref#24.#32//java/lang/StringBuilder.append:(Z)Ljava/lang/StringBuilder; #32=NameAndType#33:#34//append:(Z)Ljava/lang/StringBuilder; #33=Utf8append #34=Utf8(Z)Ljava/lang/StringBuilder; #35=Methodref#24.#36//java/lang/StringBuilder.toString:()Ljava/lang/String; #36=NameAndType#37:#38//toString:()Ljava/lang/String; #37=Utf8toString #38=Utf8()Ljava/lang/String; #39=Methodref#40.#42//java/io/PrintStream.println:(Ljava/lang/String;)V #40=Class#41//java/io/PrintStream #41=Utf8java/io/PrintStream #42=NameAndType#43:#30//println:(Ljava/lang/String;)V #43=Utf8println #44=Class#45//java/lang/String #45=Utf8java/lang/String #46=Methodref#44.#29//java/lang/String."<init>":(Ljava/lang/String;)V #47=Utf8args #48=Utf8[Ljava/lang/String; #49=Utf8s1 #50=Utf8Ljava/lang/String; #51=Utf8s2 #52=Utf8s3 #53=Utf8s4 #54=Utf8StackMapTable #55=Class#48//"[Ljava/lang/String;" #56=Utf8SourceFile #57=Utf8StringTest.java ........... //对应的方法的字节码指令,由jvm运行时解释执行。 publicstaticvoidmain(java.lang.String[]); descriptor:([Ljava/lang/String;)V flags:ACC_PUBLIC,ACC_STATIC Code: stack=4,locals=5,args_size=1 0:ldc#16//Stringilovechina,该指令是将常量池的#16处符号引用,在这里为字符串“ilovechina”符号引用push到栈顶。该指令与底下的指令2对应于程序中的Strings1="ilovechina"语句 2:astore_1//将栈顶的对象引用赋值给局部变量1. 3:ldc#16//Stringilovechina,同0处的指令,指向的是同一个符号引用处的常量。该指令与底下的指令5对应于程序中的Strings2="ilovechina"语句。 5:astore_2//将栈顶的对象引用赋值给局部变量2. 6:getstatic#18//Fieldjava/lang/System.out:Ljava/io/PrintStream; 9:new#24//classjava/lang/StringBuilder 12:dup 13:ldc#26//Stringresult: 15:invokespecial#28//Methodjava/lang/StringBuilder."<init>":(Ljava/lang/String;)V 18:aload_1 19:aload_2 20:if_acmpne27//弹出栈顶两个对象引用进行比较其是否相等,不等,转到指令27处,执行,相等执行下一条指令 23:iconst_1 24:goto28 27:iconst_0 28:invokevirtual#31//Methodjava/lang/StringBuilder.append:(Z)Ljava/lang/StringBuilder; 31:invokevirtual#35//Methodjava/lang/StringBuilder.toString:()Ljava/lang/String; 34:invokevirtual#39//Methodjava/io/PrintStream.println:(Ljava/lang/String;)V 37:new#44//classjava/lang/String,创建一个对象,该对象位于常量池#44引用处,这里为String对象,并将对象引用push到栈顶。 40:dup//拷贝栈顶一份对象引用push到栈顶。 41:ldc#16//Stringilovechina,同0,3处指令。 43:invokespecial#46//Methodjava/lang/String."<init>":(Ljava/lang/String;)V 46:astore_3 47:new#44//classjava/lang/String//创建一个对象,并将对象引用push到栈顶 50:dup 51:ldc#16//Stringilovechina,将字符串的符号引用push到栈顶。 53:invokespecial#46//Methodjava/lang/String."<init>":(Ljava/lang/String;)V,根据栈顶的对应的对象引用及字符串引用调用对象的init初始化方法,对字符串对象初始化 56:astore4//将栈顶对象引用赋值给变量4. 58:getstatic#18//Fieldjava/lang/System.out:Ljava/io/PrintStream; 61:new#24//classjava/lang/StringBuilder 64:dup 65:ldc#26//Stringresult: 67:invokespecial#28//Methodjava/lang/StringBuilder."<init>":(Ljava/lang/String;)V 70:aload_3 71:aload4 73:if_acmpne80 76:iconst_1 77:goto81 80:iconst_0 81:invokevirtual#31//Methodjava/lang/StringBuilder.append:(Z)Ljava/lang/StringBuilder; 84:invokevirtual#35//Methodjava/lang/StringBuilder.toString:()Ljava/lang/String; 87:invokevirtual#39//Methodjava/io/PrintStream.println:(Ljava/lang/String;)V 90:return ......... LineNumberTable: line7:0 line8:3 line9:6 line11:37 line12:47 line13:58 line14:90 LocalVariableTable: StartLengthSlotNameSignature 910args[Ljava/lang/String;//局部变量0 881s1Ljava/lang/String;//局部变量1 852s2Ljava/lang/String;//局部变量2 443s3Ljava/lang/String;//局部变量3 334s4Ljava/lang/String;//局部变量4
字节码中红色的部分是与我们讨论相关的。通过生成的字节码,我们可以对示例程序得出如下结论。
2). 对于Strings3=newString("ilovechina"),Strings4=newString("ilovechina"),由字节码可以看出其是调用了new指令,jvm会在运行时创建两个不同的对象,s3与s4指向的是不同的对象地址。所以s3==s4比较的结果为false。
其次,对于s3与s4对象的初始化,从字节码看出是调用对象的init方法并且传递的是常量池中”ilovechina”的引用,那么创建String对象及初始化究竟干了什么,我们可以查看通过查看String的源码及String对象生成的字节码,以便更好的了解对于newString("ilovechina")时,在对象内部是做了字符串的拷贝还是直接指向该字符串对应的常量池的地址的引用。
3.String对象的部分源码:
<SPANstyle="FONT-SIZE:14pt">publicfinalclassString implementsjava.io.Serializable,Comparable<String>,CharSequence{ /**Thevalueisusedforcharacterstorage.*/ privatefinalcharvalue[]; /**Cachethehashcodeforthestring*/ privateinthash;//Defaultto0 publicString(){ this.value=newchar[0]; }</SPAN> <SPANstyle="BACKGROUND-COLOR:#ffffff;FONT-SIZE:18pt">publicString(Stringoriginal){ this.value=original.value; this.hash=original.hash; } </SPAN>
从源码中我们看到String类里有个实例变量charvalue[],通过构造方法我们可知,对象在初始化时并没有做拷贝操作,只是将传递进来的字符串对象的地址引用赋给了实例变量value。由此我们可以初步的得出结论:即使使用newString("abc")创建了一个字符串对象时,在内存堆中为该对象分配了空间,但是在堆上并没有存储"abc"本身的任何信息,只是初始化了其内部的实例变量到"abc"字符串的引用。其实这样做也是为了节省内存的存储空间,以及提高程序的性能。
4.下面我们来看看String对象部分字节码信息:
publicjava.lang.String(); descriptor:()V flags:ACC_PUBLIC Code: stack=2,locals=1,args_size=1 0:aload_0 1:invokespecial#1//Methodjava/lang/Object."<init>":()V 4:aload_0 5:iconst_0 6:newarraychar 8:putfield#2//Fieldvalue:[C 11:return LineNumberTable: line137:0 line138:4 line139:11 publicjava.lang.String(java.lang.String); descriptor:(Ljava/lang/String;)V flags:ACC_PUBLIC Code: stack=2,locals=2,args_size=2 0:aload_0//将局部变量0push到栈顶,自身对象的引用。 1:invokespecial#1//Methodjava/lang/Object."<init>":()V弹出栈顶对象引用调用该对象的#1处的初始化方法。 4:aload_0//将自身对象引用push到栈顶。 5:aload_1//传递的字符串引用push到栈顶。 6:getfield#2//Fieldvalue:[C//弹出栈顶的字符串引用并将其赋值给#2处的实例变量,并将其存放到栈上。 9:putfield#2//Fieldvalue:[C//弹出栈顶的字符串引用及对象自身的引用并将字符串的引用赋值给本对象自身的实例变量。 12:aload_0 13:aload_1 14:getfield#3//Fieldhash:I 17:putfield#3//Fieldhash:I 20:return
从字节码的角度我们可以得出结论,newString("abc")在构造新对象时执行的是字符串引用的赋值,而不是字符串的拷贝。以上是从源码及字节码的角度来对字符串的内存分配进行的分析与总结。
以上这篇java字符串内存分配的分析与总结(推荐)就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持毛票票。