首先我們使用例子的形式看看JVM是如何處理String類型的?
1、簡單的例子
a. String s1=new String("Hello");
b. String s2="Hello";
c. Object obj=new Object();
2、生成的字節碼
JVM將上面的程序編譯後生成的字節碼如下:
a. 0: new #2; //class java/lang/String
3: dup
4: ldc #3; //String Hello
6: invokeespecial #4; //Method java/lang/String."<init>":(Ljava/lang/String;) V
9: astore_1
b. 10: ldc #3; //String Hello
12:astore_2
c. 13:new #5; //class java/lang/object
16:dup
17:invokespecial #1; //Method java/lang/Object."<init>":()V
20:astore_3
3、指令解析
new 指令格式: new indexbyte1,indexbyte2
執行過程:
要執行new指令,JVM通過計算(indexbyte1<<8)|indexbyte2生成一個指向常量池的無符號的16位索引,然後根據這個索引查找JVM常量池的入口。該索引所指向的常量池入口必須爲CONSTANT_Class_info。CONSTANT_Class_info有一個tag和一個指向常量池的索引所組成。如果該入口不存在,那麼JVM將解析這個常量池入口,該入口類型必須爲類。JVM堆中爲新的對象映像分配足夠大的空間,並將對象的實例變量設置爲默認值。最後JVM將指向新的對象的引用objectref壓入操作數棧。
dup 指令格式: dup
執行過程:
要執行dup指令,JVM複製了操作數棧頂部一個字長的內容,然後再將複製內容壓入棧。本指令能夠從操作數棧頂部複製任何單位字長的值。但絕對 不要使用它來複制操作數棧頂部任何兩個字長(long型或double型)中的一個字長。上面例中,即複製引用objectref,這時在操作數棧存在2 個引用。
ldc 指令格式: ldc,index
執行過程:
要執行ldc指令,JVM首先查找index所指定的常量池入口,在index指向的JVM常量池入口,JVM將會查找 CONSTANT_Integer_info,CONSTANT_Float_info和CONSTANT_String_info入口。如果還沒有這些 入口,JVM會解析它們。而對於上面的haha,JVM會找到CONSTANT_String_info入口,同時,將把指向被拘留String對象(由 解析該入口的進程產生)的引用壓入操作數棧。
invokespecial 指令格式: invokespecial indextype1,indextype2
執行過程:
對於該類而言,該指令是用來進行實例初始化方法的調用。上面例子中,即通過其中一個引用調用String類的構造器,初始化對象實例,讓另一個相同的引用
指向這個被初始化的對象實例,然後前一個引用彈出操作數棧。
astore_1指令格式: astore_1
astore_1指令過程:
要執行astore_1指令,JVM從操作數棧頂部彈出一個引用類型或者returnAddress類型值,然後將該值存入由索引1指定的局部變量中,即
將引用類型或者returnAddress類型值存入局部變量1。
String 類型在編碼中字面上的對象,是會進行查詢String 常量池表的(如果沒有就創建一個String 對象,然後再保存在表中)。而new String()生成的對象是不會查詢String常量池表的,它不管表中是否存在,都會創建一個新的String 對象,所以內存地址是肯定不同的。而如果調用String 方法中的intern()方法,則會在String常量池表中查找,然後把該表中的引用返回。