Java中的常量池分为三类:字符串常量池、class常量池、运行时常量池
字符串常量池
从1.7及其之后,字符串常量池从方法区移到了堆中
字符串池的实现——StringTable
String类中并没有Integer中IntegerCache对象池,String中有native方法intern()。
intern
intern()方法作用是:若字符串常量池中存在(通过#equals(Object)来判定是否存在)该字符串,则直接返回,否则,将该String对象添加到池中并返回它的引用。
/**
* Returns a canonical representation for the string object.
*
* A pool of strings, initially empty, is maintained privately by the
* class {@code String}.
*
* When the intern method is invoked, if the pool already contains a
* string equal to this {@code String} object as determined by
* the {@link #equals(Object)} method, then the string from the pool is
* returned. Otherwise, this {@code String} object is added to the
* pool and a reference to this {@code String} object is returned.
*
* It follows that for any two strings {@code s} and {@code t},
* {@code s.intern() == t.intern()} is {@code true}
* if and only if {@code s.equals(t)} is {@code true}.
*
* All literal strings and string-valued constant expressions are
* interned. String literals are defined in section 3.10.5 of the
* The Java™ Language Specification.
*
* @return a string that has the same contents as this string, but is
* guaranteed to be from a pool of unique strings.
*/
public native String intern();
intern()
class常量池
class文件是一组以字节为单位的二进制数据流,当java代码被编译为.class文件格式时,二进制数据存放在磁盘中,其中就包括class文件常量池。
class常量池包含字面量和符号引用
image.png
public class HelloWorld {
public static void main(String[] args) {
String s = "Hollis";
}
}
javac 生成HelloWorld.class文件,然后javap -v HelloWorld.class
常量池内容如下:
image.png
Java代码在编译时,并不会“拼接”好完整执行文件,而是在虚拟机加载class文件时动态连接。当虚拟机运行时,需要从常量池获得对应的符号引用,再在类创建时或运行时解析、翻译到具体的内存地址中。
运行时常量池
Java1.7之前是在方法区,也处于永久代中;Java1.7因为使用永久代存在内存泄漏问题,将永久代中的运行时常量池移动到堆内存中;Java1.8是在元空间。
只有class文件中的常量池肯定是不够的,因为我们需要在JVM中运行起来。这时候就需要一个运行时常量池,为JVM的运行服务。
运行时常量池跟class文件的常量池一一对应,它是根据class常量池来构建的。
运行时常量池分两种类型:符号引用和静态常量
String s = "a";
s就是符号引用,需要在运行期进行解析,而a是静态常量,它是不会发生变化的。
静态常量详解
运行时常量池中的静态常量是从class文件中的constant_pool构建的,可以分为两部分:String常量和数字常量。
String常量
String常量是对String对象的引用,是从class中的CONSTANT_String_info结构构建的:
CONSTANT_String_info {
u1 tag;
u2 string_index;
}
tag是结构体的标记,string_index是string在class常量池中的index。
string_index对应的class常量池的内容是一个CONSTANT_Utf8_info结构体。
CONSTANT_Utf8_info {
u1 tag;
u2 length;
u1 bytes[length];
}
CONSTANT_Utf8_info是啥呢?它就是要创建的String对象的变种UTF-8编码。
我们知道unicode的范围是从0x0000 至 0x10FFFF。
变种UTF-8就是将unicode进行编码的方式。那是怎么编码呢?
image.png
上面这个图说实话,我没怎么看懂,TODO待学习
CONSTANT_String_info运行时String常量的规则(inter):
如果String.intern之前被调用过,并且返回的结果和CONSTANT_String_info中保存的编码是一致的话,表示他们指向的是同一个String的实例。
如果不同的话,那么会创建一个新的String实例,并将运行时String常量指向该String的实例。最后会在这个String实例上调用String的intern方法。调用inter方法主要是将这个String实例加入字符串常量池。
数字常量
数字常量是从class文件中的CONSTANT_Integer_info, CONSTANT_Float_info, CONSTANT_Long_info和 CONSTANT_Double_info 构建的。
符号引用详解
符号引用也是从class中的constant_pool中构建的。
对class和interface的符号引用来自于CONSTANT_Class_info。
对class和interface中字段的引用来自于CONSTANT_Fieldref_info。
class中方法的引用来自于CONSTANT_Methodref_info。
interface中方法的引用来自于CONSTANT_InterfaceMethodref_info。
对方法句柄的引用来自于CONSTANT_MethodHandle_info。
对方法类型的引用来自于CONSTANT_MethodType_info。
对动态计算常量的符号引用来自于CONSTANT_MethodType_info。
对动态计算的call site的引用来自于CONSTANT_InvokeDynamic_info。