????????運行時常量池(Runtime Constant Pool)是每一個類或接口的常量池(Constant_Pool)的運行時表示形式,它包括了若干種不同的常量:從編譯期可知的數值字面量到必須運行期解析后才能獲得的方法或字段引用。運行時常量池扮演了類似傳統語言中符號表(Symbol Table)的角色,不過它存儲數據范圍比通常意義上的符號表要更為廣泛。
????????每一個運行時常量池都分配在Java虛擬機的方法區之中,在類和接口被加載到虛擬機后,對應的運行時常量池就被創建出來。
???????? 在創建類和接口的運行時常量池時,可能會發生如下異常情況:
? 當創建類或接口的時候,如果構造運行時常量池所需要的內存空間超過了方法區所能提供的最 大值,那Java虛擬機將會拋出一個OutOfMemoryError異常。
? ? ? ? 上面的表述實在有些晦澀,不易初學者理解,所以下面我通過一個詳細的例子,用通俗的語言一步步帶你理解“運行時常量池”是怎么工作的。
假設我們寫了一段簡單的 Java 代碼:
public class Example {public static void main(String[] args) {int number = 42;String message = "Hello, World!";System.out.println(message);}
}
第一步:代碼編譯成?.class
?文件
????????當你用?javac Example.java
?編譯這段代碼時,Java 編譯器會生成一個?Example.class
?文件。這個文件里有一個“常量池”(Constant Pool),它就像一個清單列表,把代碼里用到的“固定信息”列出來。這些信息在編譯時就已經確定了,但它們只是“符號”或者“名字”,還沒有變成程序運行時能直接用的東西。
在這個例子中,常量池里可能會記錄這些內容:
- 數字?
42
(一個整數字面量)。 - 字符串?
"Hello, World!"
(一個字符串字面量)。 - 方法引用?
System.out.println
(表示我們要調用的那個打印方法)。 - 一些類名和字段名,比如?
java/lang/System
?和?out
。
這些信息被存成一種特殊的格式,比如:
#1 = Integer 42
(表示數字 42)。#2 = String "Hello, World!"
(表示字符串)。#3 = Methodref java/lang/System.out.println
(表示打印方法)。
????????這些只是符號,不是最終的內存地址或實際數據。常量池就像一個“備忘錄”,記錄了代碼里用到的所有關鍵東西。
第二步:JVM 加載并創建運行時常量池
????????當你運行?java Example
?時,JVM 會加載?Example.class
?文件。它會把文件里的常量池拿出來,變成一個“活的”東西——這就是“運行時常量池”(Runtime Constant Pool)。這個運行時常量池存在于 JVM 的內存中(具體在方法區里),它的任務是把剛才那些符號“翻譯”成程序能用的實際內容。
比如:
- 數字?
42
:在運行時常量池里,它還是?42
,但會被關聯到程序的計算中,直接用在?int number = 42
?賦值。 - 字符串?
"Hello, World!"
:JVM 會檢查字符串池(String Pool,專門用來存字符串),如果池子里已經有?"Hello, World!"
,就直接用那個;如果沒有,就創建一個新的字符串對象,然后把它的引用存到運行時常量池里。 - 方法?
System.out.println
:運行時常量池會去查找?System
?類的具體位置,找到?out
?這個字段(它是一個?PrintStream
?對象),再找到?println
?方法的實際內存地址。這樣,程序就知道去哪里執行打印操作。
第三步:程序運行時的翻譯過程
現在程序開始執行?main
?方法:
-
int number = 42;
- JVM 直接從運行時常量池拿到?
42
,把它賦值給變量?number
。這里沒什么復雜的,因為?42
?是個簡單的數字。
- JVM 直接從運行時常量池拿到?
-
String message = "Hello, World!";
- JVM 從運行時常量池里找到?
"Hello, World!"
?的引用。因為它是字符串字面量,JVM 會確保它在字符串池里只存在一份,然后讓?message
?指向這個字符串。
- JVM 從運行時常量池里找到?
-
System.out.println(message);
- JVM 去運行時常量池查找?
System.out.println
?的符號引用。 - 它先找到?
System
?類(可能在內存地址比如?0x1234
),然后找到?out
?字段(一個?PrintStream
?對象,比如在?0x5678
),再找到?println
?方法的具體地址(比如?0x9abc
)。 - 最后,JVM 調用這個方法,把?
message
?的內容?"Hello, World!"
?打印出來。
- JVM 去運行時常量池查找?
比喻解釋:運行時常量池像一個“翻譯官”
想象你去一個陌生的國家旅游,帶了一本旅游手冊,里面寫著:
- “酒店” 在第 1 頁。
- “問候語:你好” 在第 2 頁。
- “找餐廳的方法” 在第 3 頁。
????????這本手冊就像編譯時的“常量池”,它只是記錄了信息,但你還不知道具體怎么用。到了當地,你請了個導游(運行時常量池),他拿著手冊幫你翻譯:
- “酒店” → 帶你去具體的酒店地址。
- “你好” → 教你怎么發音跟當地人打招呼。
- “找餐廳的方法” → 告訴你具體的路線。
????????運行時常量池就干這個活兒:把代碼里的符號(手冊上的詞)翻譯成實際能用的東西(地址、數據),讓程序順利跑起來。
總結
通過這個例子,可知運行時常量池的核心作用是:
- 保存編譯時確定的常量(數字、字符串、方法引用等)。
- 在程序運行時,把這些符號解析成實際的內存地址或數據。
- 確保代碼里的每一部分都能找到它需要的東西,正確執行。