目录
静态常量池
静态常量池,主要指的是在编译期被存入.class文件中的常量池。这部分常量池主要包含直接量(如文本字符串、已知的数值和符号引用)等编译期就能确定的常量。 静态常量池是Java类文件结构的一部分,它主要用于存放两大类常量:字面量(Literal)和符号引用(Symbolic References)。
静态常量池类型
主要类型包含:
1. **CONSTANT_Utf8_info**:UTF-8编码的字符串。
2. **CONSTANT_Integer_info**:整型字面量。
3. **CONSTANT_Float_info**:浮点型字面量。
4. **CONSTANT_Long_info**:长整型字面量。
5. **CONSTANT_Double_info**:双精度浮点型字面量。
6. **CONSTANT_String_info**:字符串类型的字面量。
7. **CONSTANT_Class_info**:类或接口的符号引用。
8. **CONSTANT_Fieldref_info**:字段的符号引用。9. **CONSTANT_Methodref_info**:类中方法的符号引用。
10. **CONSTANT_InterfaceMethodref_info**:接口中方法的符号引用。
11. **CONSTANT_NameAndType_info**:字段或方法的名称和描述符。
12. **CONSTANT_MethodHandle_info**:表示方法句柄。
13. **CONSTANT_MethodType_info**:表示方法类型。
14. **CONSTANT_InvokeDynamic_info**:表示引导方法调用的动态链接。
15. **CONSTANT_Module_info** 和 **CONSTANT_Package_info**:这两种类型是在Java 9中引入的,用于支持模块系统。
什么是字面量
在java文件中定义的数字或者字符串都成为字面量
int a = 1;
int b = 2;
String c = "hello"
上述代码中,1、2、“hello” 都是字面量。对应着常量池中Integer_info和String_info。
什么是符号引用
符号引用主要包含以下几种:
1、类和节点的符号引用
2、字段符号引用
3、方法符号引用
int a = 1;
String b = "hello";
public void sayHello(){}
上述代码中a、b、 sayHello都是符号应用。
运行时常量池
当Java类被加载时,静态常量池中的内容会被存入运行时常量池,也就是被加载到方法区。这个过程中,会进行动态链接,也就是把这些符号引用转换为直接引用。
字符串常量池
这里我们只讨论jdk1.8。上面我们提过,静态常量池中包含两种:字面量和符号引用。字面量中又包含了 :整型字面量、浮点型字面量、字符串字面量等等。
当静态常量池加载到内存变成运行时常量池时,将字符串字面量加载到字符串常量池中,在jdk1.8的版本中,字符串常量池存放在堆中。
在Java中,当我们创建一个字符串字面量(例如,String s = "hello")时,Java会首先在字符串常量池中查找是否已经存在值为"hello"的字符串。如果存在,Java就会返回这个已经存在的字符串的引用;如果不存在,Java就会创建一个新的字符串,放入字符串常量池,并返回这个新字符串的引用。这就是为什么两个相同的字符串字面量的引用往往是相等的。 这样做的主要目的是为了减少string对象的创建,节约内存。
intern方法
intern方法是一个native方法。当调用 s.intern() 方法时,如果字符串常量池中已经包含一个等于 s 的字符串(用 equals(Object) 方法进行比较),那么返回常量池中的字符串。否则,将引用对象放入串池中。
String s1 = new String("hello");
String s2 = "hello";
String s3 = s1.intern();
在上述代码中:
当程序执行new String("hello")的时候,会发生以下两件事情:
1. "hello"这个字符串字面量会被放入字符串常量池。如果常量池中已经存在"hello",则不会创建新的字符串对象。
2. 在堆内存中,创建一个新的String对象,这个对象的值是字符串常量池中"hello"这个字符串的引用。
之后执行s2=”hello“。 此时因为串池中已经存在”hello“,所以s2直接指向了串池中的地址。
之后再执行s3=s1.intern()。由于s1.intern执行的时候,串池中已经存在了”hello“,因此s3也指向了串池中的地址。因此:
s1 == s2 // return false
s2 == s3 // return true
在换一个例子:
String s1 = new String("he") + new String("llo");
//String s3 = "hello"; //这行执行与否将对结果产生影响
String s2 = s1.intern();
System.out.println(s1 == s2);
上述代码中,如果s3语句不被执行,则s1==s2 // true。 如果s3语句执行,则s1==s2 //false。
分析:
1、当执行第一句的时候,在串池中将会创建”he“ 和“llo” 这两个对象,同时会在堆中创建 两个普通的对象。然后生成s1 指向堆中的一个普通对象,对象的内容为“hello”。
2、如果s3的语句执行,则会在串池中创建“hello” 对象。如果不执行,则串池中没有“hello”这对象。
3、当执行s1.intern()的时候,如果s3执行,则说明串池中有“hello”对象,那么s1.intern()将会返回串池中的对象,如果s3未执行,则串池中没有,则将s1对象(全部数据)添加进行串池,并返回引用。
4、因此s1==s2的结果完全取决于s3是否执行。
底层原理
在jvm的c++方法如下:
在jvm_InternString方法中,主要执行StringTable::intern()。
基础包装类型的常量池
整型常量池
Integer i = 126;
Integer j = 126;
i == j //true
上述代码为什么返回true呢? 按理说i和j是两个不同的Integer对象,他们的内存地址是不一样的,应该返回false,但是结果缺返回true。
通过对class解析,看到jvm底层的指令为:
0 bipush 123
2 invokestatic #2 <java/lang/Integer.valueOf : (I)Ljava/lang/Integer;>
5 astore_1
6 bipush 123
8 invokestatic #2 <java/lang/Integer.valueOf : (I)Ljava/lang/Integer;>
11 astore_2
12 return
可以看到Integer i=123调用的是Integer.valueOf(),那么这个方法的内部怎么实现的呢?
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
可以看到,当时i的值介于low和high之间的时候,实际上返回的是一个缓存对象。如果非介于low和high之间,则new一个新的对象。
而low和high对应的值为-128和127。因此返回的i和j是相等的。
同理,其他的包装类型也是如此。