Java中的常量池分為三類:字符串常量池姜钳、class常量池毛甲、運行時常量池
字符串常量池
從1.7及其之后景东,字符串常量池從方法區(qū)移到了堆中
字符串池的實現(xiàn)——StringTable
String類中并沒有Integer中IntegerCache對象池闻察,String中有native方法intern()邻梆。
intern
intern()方法作用是:若字符串常量池中存在(通過#equals(Object)來判定是否存在)該字符串守伸,則直接返回,否則浦妄,將該String對象添加到池中并返回它的引用尼摹。
/**
* Returns a canonical representation for the string object.
* <p>
* A pool of strings, initially empty, is maintained privately by the
* class {@code String}.
* <p>
* 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.
* <p>
* 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}.
* <p>
* All literal strings and string-valued constant expressions are
* interned. String literals are defined in section 3.10.5 of the
* <cite>The Java™ Language Specification</cite>.
*
* @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文件是一組以字節(jié)為單位的二進制數(shù)據(jù)流,當java代碼被編譯為.class文件格式時剂娄,二進制數(shù)據(jù)存放在磁盤中蠢涝,其中就包括class文件常量池。
class常量池包含字面量和符號引用
public class HelloWorld {
public static void main(String[] args) {
String s = "Hollis";
}
}
javac 生成HelloWorld.class文件阅懦,然后javap -v HelloWorld.class
常量池內(nèi)容如下:
Java代碼在編譯時惠赫,并不會“拼接”好完整執(zhí)行文件,而是在虛擬機加載class文件時動態(tài)連接故黑。當虛擬機運行時儿咱,需要從常量池獲得對應的符號引用,再在類創(chuàng)建時或運行時解析场晶、翻譯到具體的內(nèi)存地址中混埠。
運行時常量池
Java1.7之前是在方法區(qū),也處于永久代中诗轻;Java1.7因為使用永久代存在內(nèi)存泄漏問題钳宪,將永久代中的運行時常量池移動到堆內(nèi)存中;Java1.8是在元空間扳炬。
只有class文件中的常量池肯定是不夠的吏颖,因為我們需要在JVM中運行起來。這時候就需要一個運行時常量池恨樟,為JVM的運行服務半醉。
運行時常量池跟class文件的常量池一一對應,它是根據(jù)class常量池來構(gòu)建的劝术。
運行時常量池分兩種類型:符號引用和靜態(tài)常量
String s = "a";
s就是符號引用缩多,需要在運行期進行解析呆奕,而a是靜態(tài)常量,它是不會發(fā)生變化的衬吆。
靜態(tài)常量詳解
運行時常量池中的靜態(tài)常量是從class文件中的constant_pool構(gòu)建的梁钾,可以分為兩部分:String常量和數(shù)字常量。
String常量
String常量是對String對象的引用逊抡,是從class中的CONSTANT_String_info結(jié)構(gòu)構(gòu)建的:
CONSTANT_String_info {
u1 tag;
u2 string_index;
}
tag是結(jié)構(gòu)體的標記姆泻,string_index是string在class常量池中的index。
string_index對應的class常量池的內(nèi)容是一個CONSTANT_Utf8_info結(jié)構(gòu)體冒嫡。
CONSTANT_Utf8_info {
u1 tag;
u2 length;
u1 bytes[length];
}
CONSTANT_Utf8_info是啥呢麦射?它就是要創(chuàng)建的String對象的變種UTF-8編碼。
我們知道unicode的范圍是從0x0000 至 0x10FFFF灯谣。
變種UTF-8就是將unicode進行編碼的方式。那是怎么編碼呢蛔琅?
上面這個圖說實話胎许,我沒怎么看懂,TODO待學習
CONSTANT_String_info運行時String常量的規(guī)則(inter):
- 如果String.intern之前被調(diào)用過罗售,并且返回的結(jié)果和CONSTANT_String_info中保存的編碼是一致的話辜窑,表示他們指向的是同一個String的實例。
- 如果不同的話寨躁,那么會創(chuàng)建一個新的String實例穆碎,并將運行時String常量指向該String的實例。最后會在這個String實例上調(diào)用String的intern方法职恳。調(diào)用inter方法主要是將這個String實例加入字符串常量池所禀。
數(shù)字常量
數(shù)字常量是從class文件中的CONSTANT_Integer_info, CONSTANT_Float_info, CONSTANT_Long_info和 CONSTANT_Double_info 構(gòu)建的。
符號引用詳解
符號引用也是從class中的constant_pool中構(gòu)建的放钦。
對class和interface的符號引用來自于CONSTANT_Class_info色徘。
對class和interface中字段的引用來自于CONSTANT_Fieldref_info。
class中方法的引用來自于CONSTANT_Methodref_info操禀。
interface中方法的引用來自于CONSTANT_InterfaceMethodref_info褂策。
對方法句柄的引用來自于CONSTANT_MethodHandle_info。
對方法類型的引用來自于CONSTANT_MethodType_info颓屑。
對動態(tài)計算常量的符號引用來自于CONSTANT_MethodType_info斤寂。
對動態(tài)計算的call site的引用來自于CONSTANT_InvokeDynamic_info。
參考:https://cloud.tencent.com/developer/article/1450501
http://hollischuang.gitee.io/tobetopjavaer/#/basics/java-basic/class-contant-pool
https://www.cnblogs.com/natian-ws/p/10749164.html
http://hollischuang.gitee.io/tobetopjavaer/#/basics/java-basic/Runtime-Constant-Pool
https://www.cnblogs.com/flydean/p/jvm-run-time-constant-pool.html