常量池
java中的常量池在我的理解中有三种,分别是calss常量池,运行时常量池和string 常量池
class文件常量池
class文件中的常量池是在硬盘上的,通过javap -verbose xxx.class可以看到:
Constant pool:
#1 = Methodref #7.#28 // java/lang/Object."<init>":()V
#2 = String #29 // 2
#3 = Fieldref #4.#30 // com/xx/Test.scount:Ljava/lang/String;
#4 = Class #31 // com/xx/Test
#5 = Fieldref #32.#33 // java/lang/System.out:Ljava/io/PrintStream;
#6 = Methodref #34.#35 // java/io/PrintStream.println:(Ljava/lang/Object;)V
#7 = Class #36 // java/lang/Object
#8 = Utf8 scount
#9 = Utf8 Ljava/lang/String;
#10 = Utf8 ConstantValue
所以class文件中的常量池会为每个类生成一个Constant pool的常量池,里面包含了:
1.类和接口的全限定名;
2.字段的名称描述符;
3.方法的名称和描述符。
运行时常量池
运行时常量池在JVM中InstanceKlass中的的一个属性
ConstantPool* _constants; 在方法区中的元空间,我们用HSDB来证明
我们编写一个类T0813,运行,拿到进程号,用HSDB查看如下:
String常量池
StringTable ,底层是HashTable
字符串常量池是放在堆区中,在堆区中开辟的一块空间,用于存放字符串常量池,然后整个堆区中的字符串常亮池只存在一份
其实string对象在底层维护了一个char的数组,用来存放字符串。
字符数组的存储方式
我们看下以下程序:
public class T0813 {
public static void main(String[] args) throws IOException {
char [] c = new char[]{1,2,4};
System.in.read();
}
}
通过jps拿到进程号6072,我们使用HSDB(jdk lib目录)来查看这个进程
java -cp .\sa-jdi.jar sun.jvm.hotspot.HSDB
其实在jvm中就是一个TypeArrayKlass,也就是一个TypeArrayOopDesc
上图中的char[]aarr = new char[]{“2”,“3”};在jvm中的存在形式如上图
我们在虚拟机栈中有个s1的char数组对象,然后指向了堆区的typeArrayOopDesc对象,其实堆区的字符数是对s1的引用
不同方式创建字符串在JVM中的存在形式
String str1 =“1”;
public class Str1 {
public static void main(String[] args) {
String s1 = "11";
System.out.println(s1);
}
}
上述程序共创建了几个普通对象oop,几个string对象
其实 String s1 = “11”;创建了一个string对象,一个char数组对象;
如图:在堆区创建了一个string对象和char数组对象,而char数组对象是指向了常量池typeArrayOopDesc
流程如下:
1.首先在栈中创建一个sring对象s1,然后堆区先去常量池中找字符串11是否有,如果有直接返回;
2.如果没有,在常量池中创建一个字符串对象然后指向typeArrayOopdesc,所以这个表达式在堆区创建了两个对象,string和char数组对象,而图中的
是我们的value在c++中的存在形式
String str1 =“11”;
String str2 =“11”;
两个string对象,都指向了同一个字符串11;
第一次s1创建的时候是创建了一个string对象,一个char数组对象,
而第二次s2创建的时候发现常量池有,所以这个时候就不会创建char数组对象,所以这个表达式创建了2个string对象,一个char数组对象,也就是2个对象
new Strig
public class Str1 {
public static void main(String[] args) {
String s1 = new String("11");
System.out.println(s1);
}
}
首先String s1 = new String(“11”);会在堆区创建两个string对象,一个字符串char数组对象
因为表达式的左边是一个string对象,而表达式的右边经过new String出来的,也是一个string对象,而他们两个都同时指向了typeArrayOopdesc对象,也就是字符串11,这个字符串对象是在常量池的。
两个new String
public static void main(String[] args) {
String s1 = new String("11");
String s2 = new String("11");
System.out.println(s1);
}
其实和一个new string的感念差不多,就是第一次s1创建过后,会有两个string对象,一个char数组对象,那么s2new的时候发现 常量池中已经有了字符串11,那么这个时候是指向了11的,所以这个时候只会创建一个string对象并且指向了typeArrayOopDesc,而typeArrayOopDesc是指向了常量池的11的。
字符串连接
public class Str1 {
public static void main(String[] args) {
String s1 = "1";
String s2 = "1";
String s = s1 + s2;
System.out.println(s);
}
}
字节码如下:
0: ldc #2 // String 1
2: astore_1
3: ldc #2 // String 1
5: astore_2
6: new #3 // class java/lang/StringBuilder
9: dup
10: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
13: aload_1
14: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilde
r;
17: aload_2
18: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilde
r;
21: invokevirtual #6 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
24: astore_3
25: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
可以看到字符串连接在底层就是用StrigBuilder进行的
这个程序创建了2个String,4个普通对象
4个普通对象:
2个char数组
2个string对象
为什么会创建2个string,2个char数组呢?我们来分析下:
首先,s1 和s2执行完毕过后,会创建一个string对象,一个char数组;
然后s = s1+s2进行字符串连接过后,这个时候会产生一个string,一个s1+s2的字符串数组,请注意,这个时候的s是没有放入常量池的,只是在堆区中产生的,所以我们的stirngtable不一定全部都在常量池里面,和intern有关。
再来看下以下程序:
public static void main(String[] args) {
String s1 = "1";
String s2 = "1";
String s = s1 + s2;
String ss = "11";
System.out.println(s==ss);
}
上前面理解的知识我们可以得到上面的程序
创建了3个string对象,3个char数组对象
为甚呢?因为s1 ,s2,ss都是放在常量池的,而s是没有放入常量池,所以2,3行代码创建了一个string,一个char数组 ,而第四行代码创建了一个string,一个未放入常量池的char数组对象,所以是3个string,3个char数组,并且第6行代码时false,因为内存地址都不一样,直接==是false
我们修改下程序
public static void main(String[] args) {
String s1 = "1";
String s2 = "1";
String s = s1 + s2;
s.intern();
String ss = "11";
System.out.println(s==ss);
}
这个程序增加了intern方法,那么这个时候就创建了2个string,2个char数组,因为intern把s加入了常量池,而我们的第6行代码执行的时候,发现常量池里面有11,所以就不会创建,直接返回了,所以是2个 string,2个char数组,并且s==ss 是true,因为都在常量池里面,对比的就是真实的vlaue。
所以由上述可知,我们的intern就是把我们的字符串强制放入了常量池,这也就是为什么上面我说的stringTable为什么不一定全部在常量池里面,这和intern方法有关。
最后看下不同的构造方法
public static void main(String[] args) {
String s1 = new String(new char[]{'1', '1'}, 0, 2);
System.out.println(s1);
}
上述创建了一个string,一个char,所以是两个对象,这种做法是不会放入常量池的,所以只有一个stirng,一个char