摘要:常量池探秘每個(gè)文件編譯為文件后,都將產(chǎn)生當(dāng)前類獨(dú)有的常量池,我們稱之為靜態(tài)常量池。文件中的常量池包含兩部分字面值和符號(hào)引用。方法的調(diào)用成員變量的訪問(wèn)最終都是通過(guò)運(yùn)行時(shí)常量池來(lái)查找具體地址的。其中,表示將一個(gè)常量加載到操作數(shù)棧。
java中講的常量池,通常指的是運(yùn)行時(shí)常量池,它是方法區(qū)的一部分,一個(gè)jvm實(shí)例只有一個(gè)運(yùn)行常量池,各線程間共享該運(yùn)行常量池。
java內(nèi)存模型中將內(nèi)存分為堆和棧,其中堆為線程間共享的內(nèi)存數(shù)據(jù)區(qū)域,棧為線程間私有的內(nèi)存區(qū)域。堆又包括方法區(qū)以及非方法區(qū)部分,棧包括本地方法棧、虛擬機(jī)棧等,如下圖所示:
為什么需要常量池jvm 在棧幀(frame) 中進(jìn)行操作數(shù)和方法的動(dòng)態(tài)鏈接(link),為了便于鏈接,jvm 使用常量池來(lái)保存跟蹤當(dāng)前類中引用的其他類及其成員變量和成員方法。
每個(gè)棧幀(frame)都包含一個(gè)運(yùn)行常量池的引用,這個(gè)引用指向當(dāng)前棧幀需要執(zhí)行的方法,jvm使用這個(gè)引用來(lái)進(jìn)行動(dòng)態(tài)鏈接。
在 c/c++ 中,編譯器將多個(gè)編譯期編譯的文件鏈接成一個(gè)可執(zhí)行文件或者dll文件,在鏈接階段,符號(hào)引用被解析為實(shí)際地址。java 中這種鏈接是在程序運(yùn)行時(shí)動(dòng)態(tài)進(jìn)行的。
常量池探秘每個(gè) java 文件編譯為 class 文件后,都將產(chǎn)生當(dāng)前類獨(dú)有的常量池,我們稱之為靜態(tài)常量池。class 文件中的常量池包含兩部分:字面值(literal)和符號(hào)引用(Symbolic Reference)。其中字面值可以理解為 java 中定義的字符串常量、final 常量等;符號(hào)引用指的是一些字符串,這些字符串表示當(dāng)前類引用的外部類、方法、變量等的引用地址的抽象表示形式,在類被jvm裝載并第一次使用這些符號(hào)引用時(shí),這些符號(hào)引用將會(huì)解析為直接引用。符號(hào)常量包含:
類和接口的全限定名
字段的名稱和描述符
方法的名稱和描述符
jvm在進(jìn)行類裝載時(shí),將class文件中常量池部分的常量加載到方法區(qū)中,此時(shí)方法區(qū)中的保存常量的邏輯區(qū)域稱之為運(yùn)行時(shí)常量區(qū)。
使用javap -verbose 命令可以查看class字節(jié)碼的詳細(xì)信息,其中包含了編譯期確定的靜態(tài)常量池。
public class StringTest { public static void main(String[] args){ String s = new String("abc"); String s2 = s.intern(); System.out.println(s2 == s); String s3 = (s + s2); System.out.println(s3 == s3.intern()); } }
上述代碼javap -verbose后得到(只拿出常量池部分):
major version: 52 Constant pool: #1 = Methodref #13.#26 // java/lang/Object."":()V #2 = Class #27 // java/lang/String #3 = String #28 // abc #4 = Methodref #2.#29 // java/lang/String." ":(Ljava/lang/String;)V #5 = Methodref #2.#30 // java/lang/String.intern:()Ljava/lang/String; #6 = Fieldref #31.#32 // java/lang/System.out:Ljava/io/PrintStream; #7 = Methodref #33.#34 // java/io/PrintStream.println:(Z)V #8 = Class #35 // java/lang/StringBuilder #9 = Methodref #8.#26 // java/lang/StringBuilder." ":()V #10 = Methodref #8.#36 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; #11 = Methodref #8.#37 // java/lang/StringBuilder.toString:()Ljava/lang/String; #12 = Class #38 // StringTest #13 = Class #39 // java/lang/Object #14 = Utf8 #15 = Utf8 ()V #16 = Utf8 Code #17 = Utf8 LineNumberTable #18 = Utf8 main #19 = Utf8 ([Ljava/lang/String;)V #20 = Utf8 StackMapTable #21 = Class #40 // "[Ljava/lang/String;" #22 = Class #27 // java/lang/String #23 = Class #41 // java/io/PrintStream #24 = Utf8 SourceFile #25 = Utf8 StringTest.java #26 = NameAndType #14:#15 // " ":()V #27 = Utf8 java/lang/String #28 = Utf8 abc #29 = NameAndType #14:#42 // " ":(Ljava/lang/String;)V #30 = NameAndType #43:#44 // intern:()Ljava/lang/String; #31 = Class #45 // java/lang/System #32 = NameAndType #46:#47 // out:Ljava/io/PrintStream; #33 = Class #41 // java/io/PrintStream #34 = NameAndType #48:#49 // println:(Z)V #35 = Utf8 java/lang/StringBuilder #36 = NameAndType #50:#51 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder; #37 = NameAndType #52:#44 // toString:()Ljava/lang/String; #38 = Utf8 StringTest #39 = Utf8 java/lang/Object #40 = Utf8 [Ljava/lang/String; #41 = Utf8 java/io/PrintStream #42 = Utf8 (Ljava/lang/String;)V #43 = Utf8 intern #44 = Utf8 ()Ljava/lang/String; #45 = Utf8 java/lang/System #46 = Utf8 out #47 = Utf8 Ljava/io/PrintStream; #48 = Utf8 println #49 = Utf8 (Z)V #50 = Utf8 append #51 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder; #52 = Utf8 toString
我們可以看到,常量池共包含52個(gè)常量。#1 是一個(gè)類中方法的符號(hào)引用,它由 #13 和 #26 兩個(gè)utf8編碼的字符串構(gòu)成;#3 是程序中定義的 String 類型的字面值 "abc",它包含指向一個(gè)utf8編碼字符串 "abc" 的索引 #28。
方法的調(diào)用、成員變量的訪問(wèn)最終都是通過(guò)運(yùn)行時(shí)常量池來(lái)查找具體地址的。
String 常量池運(yùn)行時(shí)常量池有一種 String 類型的常量,即通常我們所說(shuō)的字符串字面值,所有的字符串字面值組成一個(gè) String 常量表。String常量表并不是一成不變的,程序運(yùn)行時(shí)可以動(dòng)態(tài)添加字符串常量,使用String的intern()可以動(dòng)態(tài)的添加String常量。但
jvm 確保兩個(gè)在值上完全相等的字符串字面值(即其中包含的字符序列是相同的,使用equals()來(lái)判斷)指向同一個(gè) String 實(shí)例。
如:
String s1 = "abc"; String s2 = "abc"; System.out.println(s1 == s2); // true
上述代碼中的字符串 s1 和 s2 將指向同一個(gè) String 實(shí)例。實(shí)際上通過(guò)查看class文件,我們可以看到,在編譯后,靜態(tài)常量池中已經(jīng)包含了一個(gè) String 類型的字面值 "abc",程序運(yùn)行時(shí)只是從常量池中獲取這個(gè)String字面值的引用地址,并賦值給變量 s1 和變量 s2。
Constant pool: #1 = Methodref #6.#19 // java/lang/Object."":()V #2 = String #20 // abc ······ #20 = Utf8 abc public static void main(java.lang.String[]); ······ Code: stack=3, locals=3, args_size=1 0: ldc #2 // String abc 2: astore_1 3: ldc #2 // String abc
其中,ldc 表示將一個(gè)常量加載到操作數(shù)棧。
String 的 intern() 是一個(gè)native方法,返回的是一個(gè)String對(duì)象的標(biāo)準(zhǔn)表示。當(dāng)調(diào)用該方法時(shí),如果運(yùn)行時(shí)常量池中已經(jīng)存在與之相等(equal())的字符串,則直接返回常量池中的字符串引用,否則將此字符串添加到池中,并返回。
String s1 = "abc"; String s2 = new String("abc"); System.out.println(s1 == s2); //返回 false System.out.println(s1.equals(s2)); //返回 true System.out.println(s1 == s2.intern()); //返回 true
上述代碼中,雖然 s1 和 s2 中的值是相同的,但是他們指向的并不是同一個(gè)對(duì)象,但 s2 的標(biāo)準(zhǔn)化表示和s1是同一個(gè) String 對(duì)象,都是編譯期確定的常量池中的 "abc"。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/65567.html
摘要:那方法區(qū)里都存著什么呢先拋結(jié)論靜態(tài)變量常量類信息構(gòu)造方法接口定義運(yùn)行時(shí)常量池存在方法區(qū)中。動(dòng)態(tài)常量池運(yùn)行時(shí)常量池是方法區(qū)的一部分,是一塊內(nèi)存區(qū)域。文件常量池將在類加載后進(jìn)入方法區(qū)的運(yùn)行時(shí)常量池中存放。 一、方法區(qū)與永久代 這兩個(gè)是非常容易混淆的概念,永久代的對(duì)象放在方法區(qū)中,就會(huì)想當(dāng)然地認(rèn)為,方法區(qū)就等同于持久代的內(nèi)存區(qū)域。事實(shí)上兩者是這樣的關(guān)系: 《Java虛擬機(jī)規(guī)范》只是規(guī)定了有方...
摘要:證明返回常量池中已存在的對(duì)象,不等于新建的對(duì)象。為什么要設(shè)計(jì)成一下內(nèi)容來(lái)自發(fā)現(xiàn)百度的中文版本基本也是此文的翻譯版。總之,安全性和字符串常量池緩存是被設(shè)計(jì)成不可變的主要原因。 String是Java中最常用的類,是不可變的(Immutable), 那么String是如何實(shí)現(xiàn)Immutable呢,String為什么要設(shè)計(jì)成不可變呢? 前言 關(guān)于String,收集一波基礎(chǔ),來(lái)源標(biāo)明最后,不確...
摘要:為了減少在中創(chuàng)建的字符串的數(shù)量,字符串類維護(hù)了一個(gè)字符串常量池。但是當(dāng)執(zhí)行了方法后,將指向字符串常量池中的那個(gè)字符串常量。由于和都是字符串常量池中的字面量的引用,所以。究其原因,是因?yàn)槌A砍匾4娴氖且汛_定的字面量值。 String,是Java中除了基本數(shù)據(jù)類型以外,最為重要的一個(gè)類型了。很多人會(huì)認(rèn)為他比較簡(jiǎn)單。但是和String有關(guān)的面試題有很多,下面我隨便找兩道面試題,看看你能不能...
摘要:另外,為了線程切換后能恢復(fù)到正確的執(zhí)行位置,每條線程都需要有一個(gè)獨(dú)立的程序計(jì)數(shù)器,各線程之間計(jì)數(shù)器互不影響,獨(dú)立存儲(chǔ),我們稱這類內(nèi)存區(qū)域?yàn)榫€程私有的內(nèi)存。運(yùn)行時(shí)常量池運(yùn)行時(shí)常量池是方法區(qū)的一部分。 寫在前面(常見面試題) 基本問(wèn)題: 介紹下 Java 內(nèi)存區(qū)域(運(yùn)行時(shí)數(shù)據(jù)區(qū)) Java 對(duì)象的創(chuàng)建過(guò)程(五步,建議能默寫出來(lái)并且要知道每一步虛擬機(jī)做了什么) 對(duì)象的訪問(wèn)定位的兩種方式(句...
摘要:類是類它內(nèi)部的方法也默認(rèn)被修飾不能重寫字符串常量池當(dāng)這樣聲明一個(gè)字符串會(huì)檢測(cè)字符串常量池中是否存在這個(gè)值的字符串如果存在就直接賦值給否則創(chuàng)建一個(gè)新的再賦值給當(dāng)連續(xù)用同樣的方式聲明兩個(gè)字符串并作比較結(jié)果為這個(gè)操作符比較的是什么對(duì)于基本變量比較 String類是final類,它內(nèi)部的方法也默認(rèn)被final修飾,不能重寫. 字符串常量池 當(dāng)這樣聲明一個(gè)字符串 String str = h...
閱讀 1442·2023-04-25 17:18
閱讀 1882·2021-10-27 14:18
閱讀 2124·2021-09-09 09:33
閱讀 1840·2019-08-30 15:55
閱讀 2016·2019-08-30 15:53
閱讀 3440·2019-08-29 16:17
閱讀 3429·2019-08-26 13:57
閱讀 1730·2019-08-26 13:46