日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

java基础(八) 深入解析常量池与装拆箱机制

發(fā)布時(shí)間:2025/3/20 编程问答 14 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java基础(八) 深入解析常量池与装拆箱机制 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

###引言

本文將介紹常量池 與 裝箱拆箱機(jī)制,之所以將兩者合在一起介紹,是因?yàn)榫W(wǎng)上不少文章在談到常量池時(shí),將包裝類的緩存機(jī)制,java常量池,不加區(qū)別地混在一起討論,更有甚者完全將這兩者視為一個(gè)整體,給初學(xué)者帶來不少困擾,我就是過來的。同時(shí),也因?yàn)榘b類的緩存 與 字符串常量池的思想是一樣的,很容易混淆,但是實(shí)現(xiàn)方式是不一樣的。
###一、常量池

在介紹常量池前,先來介紹一下常量、字面常量、符號(hào)常量的定義。

常量 可分為 字面常量(也稱為直接常量)和 符號(hào)常量。

字面常量: 是指在程序中無需預(yù)先定義就可使用的數(shù)字、字符、boolen值、字符串等。簡單的說,就是確定值的本身。如 10,2L,2.3f,3.5,“hello”,‘a(chǎn)’,true、false、null 等等。

符號(hào)常量: 是指在程序中用標(biāo)識(shí)符預(yù)先定義的,其值在程序中不可改變的量。如 final int a = 5;

常量池

常量池引入的 目的 是為了避免頻繁的創(chuàng)建和銷毀對(duì)象而影響系統(tǒng)性能,其實(shí)現(xiàn)了對(duì)象的共享。這是一種 享元模式 的實(shí)現(xiàn)。
##二、 java常量池

Java的常量池可以細(xì)分為以下三類:

  • 量池,編譯階段)
  • 運(yùn)行時(shí)常量池(又稱動(dòng)態(tài)常量池,運(yùn)行階段)
  • 字符串常量池(全局的常量池)

##1. class文件常量池

class文件常量池,也被稱為 靜態(tài)常量池 ,它是.class文件所包含的一項(xiàng)信息。用于存放編譯器生成的各種字面量(Literal)和符號(hào)引用(Symbolic References)。
常量池在.class文件的位置

字面量: 就是上面所說的字面常量。
符號(hào)引用: 是一組符號(hào)來描述所引用的目標(biāo),符號(hào)可以是任何形式的字面量,只要使用時(shí)能無歧義地定位到目標(biāo)即可(它與直接引用區(qū)分一下,直接引用 一般是指向方法區(qū)的本地指針,相對(duì)偏移量或是一個(gè)能間接定位到目標(biāo)的句柄)。符號(hào)引用可以看作是一個(gè)虛擬地址,只有在JVM加載完類,確認(rèn)了字面量的地址,才會(huì)將 符號(hào)引用 換成 直接引用。一般包括下面三類常量:

  • 類和接口的全限定名
  • 字段的名稱和描述符
  • 方法的名稱和描述符

常量池的信息

###2. 運(yùn)行時(shí)常量池

運(yùn)行時(shí)常量池,又稱為 動(dòng)態(tài)常量池 ,是JVM在完成加載類之后將class文件中常量池載入到內(nèi)存中,并保存在方法區(qū)中。也就是說,運(yùn)行時(shí)常量池中的常量,基本來源于各個(gè)class文件中的常量池。 運(yùn)行時(shí)常量池相對(duì)于CLass文件常量池的另外一個(gè)重要特征是具備 動(dòng)態(tài)性 ,Java語言并不要求常量一定只有編譯期才能產(chǎn)生,也就是并非預(yù)置入CLass文件中常量池的內(nèi)容才能進(jìn)入方法區(qū)運(yùn)行時(shí)常量池,運(yùn)行期間也可能將新的常量放入池中,這種特性被開發(fā)人員利用比較多的就是String類的intern()方法。

jvm在執(zhí)行某個(gè)類的時(shí)候,必須經(jīng)過加載、連接、初始化,而連接又包括驗(yàn)證、準(zhǔn)備、解析三個(gè)階段。而當(dāng)類加載到內(nèi)存中后,jvm就會(huì)將class常量池中的內(nèi)容存放到運(yùn)行時(shí)常量池中,也就是說,每個(gè)class對(duì)應(yīng)運(yùn)行時(shí)常量池中的一個(gè)獨(dú)立空間,每個(gè)class文件存放的位置互不干擾。而在解析階段,就會(huì)將符號(hào)引用替換成對(duì)應(yīng)的直接引用。
??不過,String類型 的字面常量要注意:并不是直接在堆上分配空間來創(chuàng)建對(duì)象的,JVM為String 字符串額外維護(hù)了一個(gè)常量池 字符串常量池,所以遇到字符串常量是要先去字符串池中尋找是否有重復(fù),如果有,則返回對(duì)應(yīng)的引用。否則,才創(chuàng)建并添加到字符串常量池中。換句話說,對(duì)于String類型的字面常量,必須要在 字符串常量池 中維護(hù)一個(gè)全局的引用。
##3. 字符串常量池(string pool也有叫做string literal pool)

字符串常量池存儲(chǔ)的就是字符串的字面常量。詳細(xì)一點(diǎn),字符串常量池里的內(nèi)容是在類加載完成,經(jīng)過驗(yàn)證,準(zhǔn)備階段之后在堆中生成字符串對(duì)象實(shí)例,然后將該字符串對(duì)象實(shí)例的引用值存到string pool中(記住:string pool中存的是引用值而不是具體的實(shí)例對(duì)象,具體的實(shí)例對(duì)象是在堆中開辟的一塊空間存放的。)。
在HotSpot VM里實(shí)現(xiàn)的string pool功能的是一個(gè)StringTable類,它是一個(gè)哈希表,里面存的是駐留字符串(也就是我們常說的用雙引號(hào)括起來的)的引用(而不是駐留字符串實(shí)例本身),也就是說在堆中的某些字符串實(shí)例被這個(gè)StringTable引用之后就等同被賦予了”駐留字符串”的身份。這個(gè)StringTable在每個(gè)HotSpot VM的實(shí)例只有一份,被所有的類共享。

運(yùn)行時(shí)常量池 與 字符串常量池 的區(qū)別

字符串常量池是位于運(yùn)行時(shí)常量池中的。

網(wǎng)上有不少文章是將字符串常量池作為運(yùn)行時(shí)常量池同等來說,我一開始也以為這兩者就是同一個(gè)東西,其實(shí)不然。運(yùn)行時(shí)常量池 與 字符串常量池 在HotSpot的JDK1.6以前,都是放在方法區(qū)的,JDK1.7就將字符串常量池移到了堆外內(nèi)存中去。運(yùn)行時(shí)常量池 為每一個(gè)Class文件的常量池提供一個(gè)運(yùn)行時(shí)的內(nèi)存空間;而字符串常量池則為所有Class文件的String類型的字面常量維護(hù)一個(gè)公共的常量池,也就是Class文件的常量池加載進(jìn)運(yùn)行時(shí)常量池后,其String字面常量的引用指向要與字符串常量池的維護(hù)的要一致。

我們來幾個(gè)例子理解一下常量池

@ Example 1 ?簡單的例子

public class Test_6 { public static void main(String[] args) {String str = "Hello World!"; } }

我們使用使用javap -v MyTest.class 查看class文件的字節(jié)碼,經(jīng)javap 處理可以輸出我們能看懂的信息。如下圖:

class文件的索引#16位置(第16個(gè)常量池項(xiàng))存儲(chǔ)的是 一個(gè)描述了字符串字面常量信息(類型,以及內(nèi)容索引)的數(shù)據(jù)結(jié)構(gòu)體,這個(gè)結(jié)構(gòu)體被稱為CONSTANT_String_info。這個(gè)結(jié)構(gòu)體并沒有存儲(chǔ)字符串的內(nèi)容,而是存儲(chǔ)了一個(gè)指向字符串內(nèi)容的索引–#17,即第17項(xiàng)存儲(chǔ)的是Hello World 的二進(jìn)制碼。

@ Example 2 ?String的+運(yùn)算例子

我們再來看一個(gè)比較復(fù)雜的例子

public class Test_6 { public static void main(String[] args) {String str_aa = "Love";String str_bb = "beautiful" + " girl";String str_cc = str_aa+" China"; } }

同樣,查看class文件的字節(jié)碼信息:

??class文件的常量池保存了Love、beautiful girl、China,但卻沒有 Love China。為什么 str_bb 與 str_cc 都是通過 + 鏈接得到的,為什么str_cc的值沒有出現(xiàn)在常量池中,而str_bb的值卻出現(xiàn)了。

這是因?yàn)閟tr_bb的值是由兩個(gè)常量計(jì)算得到的,這種只有常量的表達(dá)式計(jì)算在編譯期間由編譯器計(jì)算得到的,要記住,能由編譯器完成的計(jì)算,就不會(huì)拖到運(yùn)行期間來計(jì)算。
??而str_cc的計(jì)算中包含了變量str_aa,涉及到變量的表達(dá)式計(jì)算都是在運(yùn)行期間計(jì)算的,因?yàn)樽兞渴菬o法在編譯期間確定它的值,特別是多線程下,同時(shí)得到結(jié)果是CPU動(dòng)態(tài)分配空間存儲(chǔ)的,也就是說地址也無法確定。我們再去細(xì)看,就會(huì)發(fā)現(xiàn)常量池中的包含了StringBuilder以及其方法的描述信息,其實(shí),這個(gè)StringBuilder是為了計(jì)算str_aa+" China"表達(dá)式,先調(diào)用append()方法,添加兩個(gè)字符串,在調(diào)用toString()方法,返回結(jié)果。也就是說,在運(yùn)行期間,String字符串通過 + 來鏈接的表達(dá)式計(jì)算都是通過創(chuàng)建StringBuilder來完成的

@ Example 3 ?String新建對(duì)象例子

下面的例子,str_bb的值是直接通過new新建一個(gè)對(duì)象,觀察靜態(tài)常量池。

public class MyTest { public static void main(String[] args) {String str_bb = new String("Hello"); } }

查看對(duì)應(yīng)class文件的字節(jié)碼信息:

??通過new新建對(duì)象的操作是在運(yùn)行期間才完成的,為什么這里仍舊在class文件的常量池中出現(xiàn)呢?這是因?yàn)?#34;Hello"本身就是一個(gè)字面常量,這是很容易讓人忽略的。有雙引號(hào)包裹的都是字面常量。同時(shí),new創(chuàng)建一個(gè)String字符串對(duì)象,確實(shí)是在運(yùn)行時(shí)完成的,但這個(gè)對(duì)象將不同于字符串常量池中所維護(hù)的常量。
###二、自動(dòng)裝箱拆箱機(jī)制 與 緩存機(jī)制

先來簡單介紹一下自動(dòng)裝箱拆箱機(jī)制
##1、自動(dòng)裝拆箱機(jī)制介紹

裝箱: 可以自動(dòng)將基本類型直接轉(zhuǎn)換成對(duì)應(yīng)的包裝類型。
拆箱: 自動(dòng)將包裝類型轉(zhuǎn)換成對(duì)應(yīng)的基本類型值;

//普通的創(chuàng)建對(duì)象方式Integer a = new Integer(5);//裝箱Integer b = 5;//拆箱int c = b+5;

##2. 自動(dòng)裝箱拆箱的原理

裝箱拆箱究竟是是怎么實(shí)現(xiàn),感覺有點(diǎn)神奇,居然可以使基本類型與包裝類型快速轉(zhuǎn)換。我們再稍微簡化上面的例子:

public class Test_6 { public static void main(String[] args) {//裝箱Integer b = 5;//拆箱int c = b+5; } }

依舊使用 javap -v Test_6.class 查看這個(gè)類的class文件的字節(jié)碼信息,如下圖:

??可以從class的字節(jié)碼發(fā)現(xiàn),靜態(tài)常量池中,由Integer.valueOf() 和 Integer.initValue() 這兩個(gè)方法的描述。這就有點(diǎn)奇怪,例子中的代碼中并沒有調(diào)用這兩個(gè)方法,為什么編譯后會(huì)出現(xiàn)呢?

感覺還是不夠清晰,我們換另一種反編譯工具來反編譯一下,這次我們反編譯回java代碼,使用命令 jad Test_6.class ,得到的反編譯代碼如下:

public class Test_6 {public static void main(String args[]){Integer b = Integer.valueOf(5);int c = b.intValue() + 5;} }

這回就非常直觀明了了。所謂裝箱拆箱并沒有多厲害,還是要通過調(diào)用Integer.valueOf()(裝箱) 和 Integer.initValue()(拆箱)來完成的。也就是說,自動(dòng)裝箱拆箱機(jī)制是一種語法簡寫,為了方便程序員,省去了手動(dòng)裝箱拆箱的麻煩,變成了自動(dòng)裝箱拆箱

判別是裝箱還是拆箱

在下面的兩個(gè)例子中,可能會(huì)讓你很迷惑:不知道到底使用了裝箱,還是使用了拆箱。

Integer x = 1;Integer y = 2;Integer z = x+y;

這種情況其實(shí)只要仔細(xì)想一下便可以知道:這是 先拆箱再裝箱。因?yàn)镮nteger類型是引用類型,所以不能參與加法運(yùn)算,必須拆箱成基本類型來求和,在裝箱成Integer。如果改造上面的例子,把Integer變成Short,則正確代碼如下:

Short a = 5;Short b = 6;Short c = (short) (a+b);

##3. 包裝類的緩存機(jī)制

我們先來看一個(gè)例子

public class MyTest {public static void main(String[] args) {Integer a = 5;Integer b = 5;Integer c = 129;Integer d = 129;System.out.println("a==b "+ (a == b));System.out.println("c==d "+ (c == d));} }

運(yùn)行結(jié)果:

a == b ?true
c == d ?false

咦,為什么是a和b所指向的是一個(gè)對(duì)象呢?難道JVM在類加載時(shí)也為包裝類型維護(hù)了一個(gè)常量池?如果是這樣,為什么變量c、d的地址不一樣。事實(shí)上,JVM確實(shí)沒有為包裝類維護(hù)一個(gè)常量池。變量a、b、c、d是由裝箱得到的,根據(jù)前面所說的,裝箱其實(shí)是編譯器自動(dòng)添加了Integer.valueOf() 方法。秘密應(yīng)該就在這個(gè)方法內(nèi),那么我們看一下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);}

代碼很簡單,判斷裝箱所使用的基本類型值是否在 [ IntegerCache.low, IntegerCache.high] 的范圍內(nèi),如果在,返回IntegerCache.cache數(shù)組中對(duì)應(yīng)下標(biāo)的元素。否則,才新建一個(gè)對(duì)象。我們繼續(xù)深入查看 IntegerCache 的源碼,如下:

private static class IntegerCache {static final int low = -128;static final int high;static final Integer cache[];static {// high value may be configured by propertyint h = 127;String integerCacheHighPropValue =sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");//獲取上限值if (integerCacheHighPropValue != null) {try {int i = parseInt(integerCacheHighPropValue);i = Math.max(i, 127);// Maximum array size is Integer.MAX_VALUEh = Math.min(i, Integer.MAX_VALUE - (-low) -1);} catch( NumberFormatException nfe) {// If the property cannot be parsed into an int, ignore it.}}high = h;//創(chuàng)建數(shù)組cache = new Integer[(high - low) + 1];int j = low;//填充數(shù)組for(int k = 0; k < cache.length; k++)cache[k] = new Integer(j++);// range [-128, 127] must be interned (JLS7 5.1.7)assert IntegerCache.high >= 127;}private IntegerCache() {}}

從源碼中,可以知道,IntegerCache.cache是一個(gè)final的Integer數(shù)組,這個(gè)數(shù)組存儲(chǔ)的Integer對(duì)象元素的值范圍是[-128,127]。而且這個(gè)數(shù)組的初始化代碼是包裹在static代碼塊中,也就是說IntegerCache.cache數(shù)組的初始化是在類加載時(shí)完成的。

再看回上面的例子,變量a和b的使用的基本類型值為5,超出[-128,127]的范圍,所以就使用緩存數(shù)組中的元素,所以a、b的地址是一樣的。而c、d使用的基本類型值為129,超出緩存范圍,所以都是各自在堆上創(chuàng)建一個(gè)對(duì),地址自然就不一樣了。

###包裝類緩存總結(jié)與補(bǔ)充:

  • 包裝類與String類很相似,都是非可變類,即一經(jīng)創(chuàng)建后,便不可以修改。正因?yàn)檫@種特性,兩者的對(duì)象實(shí)例在多線程下是安全的,不用擔(dān)心異步修改的情況,這為他們實(shí)現(xiàn)共享提供了很好的保證,只需創(chuàng)建一個(gè)對(duì)象共享便可。
  • 包裝類的共享實(shí)現(xiàn)并不是由JVM來維護(hù)一個(gè)常量池,而是使用了緩存機(jī)制(數(shù)組),而且這個(gè)緩存是在類加載時(shí)完成初始化,并且不可再修改。
  • 包裝類的數(shù)組緩存范圍是有限,只緩存基本類型值在一個(gè)字節(jié)范圍內(nèi),也就是說 -128 ~ 127。(Character的范圍是 0~127)
  • 目前并不是所有包裝類都提供緩存機(jī)制,只有Byte、Character、Short、Integer 4個(gè)包裝類提供,Long、Float、Double 不提供。

作者:jinggod
出處:http://www.cnblogs.com/jinggod/p/8425748.html

《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀

總結(jié)

以上是生活随笔為你收集整理的java基础(八) 深入解析常量池与装拆箱机制的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。