1. 程式人生 > >java的變數和常量以及常量池

java的變數和常量以及常量池

常量池的好處 為了避免頻繁的建立和銷燬物件而影響系統性能,其實現了物件的共享,節省記憶體空間,常量池中所有相同的字串常量被合併只佔用一個空間,節省運算時間比較兩個引用變數只用==判斷引用是否相等就可以判斷實際值是否相等,在需要一個物件時就可以從池中取出來一個在需要重複建立相等變數時節省了大量時間。所以八個基本類都實現了常量池(浮點型別的包裝類沒有實現,整型的包裝類也只有在對應值)。 java的常量池分為三種形態 (1)class檔案常量池:class檔案中的常量池,class檔案中不僅僅包含類的版本、欄位、方法、介面等資訊,還有就是佔用class檔案絕大部分空間的常量池主要存放字面量和符號引用(包括類和介面的全限定名,欄位的名稱和描述符,方法的名稱和描述符),每個class檔案常量池都對應一個執行時常量池。即在編譯的時候如果發現其它類方法的呼叫或者對其它類欄位的引用的話,記錄進class檔案中的只是一個文字形式的符號引用 (2)執行時常量池
:是JVM在完成類的裝載後,將存在於class檔案中的常量池載入到記憶體中並儲存到方法區(即java在執行某個類的時候必須經過載入、連線(包括驗證、準備、解析)、初始化),在類載入到記憶體中時就會將class常量池的內容存放到執行時常量池,執行時常量也是每個類都有一個,在經過解析的時候就會根據文字資訊把符號引用替換成直接引用,解析的過程會查詢全域性字串池以保證執行時常量池所引用的字串與全域性字串池是一樣的 可與用intern的方法進行擴充,我們常說的常量池就是指方法區中的執行時常量池,它是方法區的一部分,用於存放編譯期生成的字面量和符號應用這部分內容將在類載入後進入方法區的時候存入到執行時常量池,而且執行時常量池還有更重要的特性“動態性”,java要求編譯期的常量池的內容可以放入池中。 java的八個基本類的包裝類大部分都實現了常量池技術,但是String也實現了常量池也就出現了全域性字串常量池 (3)全域性字串池
:這裡的內容是在類載入完成後經過驗證,準備階段之後在堆中生成字串物件例項,然後將該字串物件的例項引用值存到該全域性字串池中(這裡存的是引用值而不是具體的例項物件,具體的例項物件在堆中開闢了一塊記憶體空間),實現該功能的是一個String Table類,它是一個雜湊表裡面存的是駐留字串(雙引號括起來的)的引用(意思是不是字串的本體),在每個JVM裡只有一份。 只有更好的關注編譯期的行為,才能更好的理解常量池,執行時的常量池的常量,基本來源於各個class檔案的常量池,程式執行時,除非手動向常量池中新增常量(例如intern方法)否則JVM不會自動新增常量到常量池,實際上有多種常量池比如整形常量池等等,但是數值型別的常量池不可以手動新增常量,程式啟動時常量池中的常量就已經確定了。 比如一個程式在經過編譯之後在該類的class常量池中存放一些符號引用,然後類載入後將class常量池中的符號引用轉存到執行時常量池在經過驗證準備階段之後在堆中生成駐留字串的例項物件,將這個物件的引用存到全域性字串池,在解析階段要把執行時常量池中的符號引用替換成直接引用,就會直接查詢String Table保證裡面的引用值和執行時常量池中的引用值一致。 字串駐留
首先要知道可變類和不可變類 可變類:當獲得這個類的例項引用,可以改變類的例項內容 不可變類:當獲得這個類的例項引用,不可以改變這個例項的內容,不可變類的例項一但被建立,其內在成員變數的值就不能被改變,比如String 為什麼使用不可變物件,以Sring為例:不可變物件可以提高Sting Pool的效率(即字串常量池)和安全性,如果知道一個物件是不可變的,那麼就需要拷貝這個物件的內容時,就不需要複製本身而是複製地址,同時不可變的物件對於執行緒也是安全的不會因為多執行緒同時進行導致值的改變。 java會確保一個字元常量只有一個拷貝,雙引號裡的都是字串它們在編譯期就被確定了,但是用new String()建立的字串不是常量不能再編譯期確定,所以new String()建立的字串不能放進常量池中,有自己的空間,在使用intern之後可以將其儲存到一個全域性String表裡,如果具有相同值的Unicode字串在這個表裡,那麼該方法返回表中已有字串的地址並且不會將外部的字串放入常量池,如果不同會將外部字串放入池中,並返回字串的控制代碼(但是不是將自己的地址註冊到常量池中,因為S1 == S1.intern()的結果是false,如果是將地址註冊到常量池中那麼這個使用==來測試是否為同一個字串的結果應該是true,也就是說之前的字串還是存在的)。 String是不可變的所以在進行拼接的時候,會生成很多臨時變數,這是建議使用StringBuffer的原因,因為StringBuffer可變。
常量池的具體結構 在java語言中一切都是動態的,編譯時如果發現對其他類方法的呼叫或者對其他類欄位的引用的語句,記錄進class檔案中的只能是一個文字形式的符號引用,在連線過程中,虛擬機器根據這個文字資訊去查詢對應的方法或欄位,所以與java的所謂常量不同,class檔案中的常量內容豐富,這些常量集中在class中的一個區域存放,一個接著一個就是常量池,在java程式中有很多東西是永恆的,不會再執行過程中變化,比如一個類的名字,一個類欄位的名字/所屬型別,一個類方法的名字/返回型別/引數名與所屬的型別,一個常量,還有在程式中的字面值,這些在JVM解釋執行的時候非常重要,會用一部分位元組分類儲存這些程式碼,這些位元組就是常量池但是隻有JVM載入class後在方法區為它們開闢了空間 常量池中的常量表,這些常量表之間又有不同,class檔案中一共有11種常量表: (1)_Utf8:用UTF-8編碼方式來表示程式中所有的重要常量字串 (3-6)_Integer、_Float、_Long、_Double:所有基本資料型別的字面值 (7)_Class:使用符號引用來表示類或介面,知道所有類名都以_Utf8表的形式儲存,但是不知道_Utf8表中哪些字串是類名哪些是方法名,因此必須用一個指向類名字串的符號引用常量來表明 (8)_String:指向包含字串字面值的Utf8表 (9-11)_Fieldref、_Methodren、_InterfaceMethodref:指向包含該欄位或方法的名字和描述符的Utf8表以及指向包含該欄位或方法的名字和描述符_NameAndType表 (12)_NameAndType:指向包含該欄位或方法的名字和描述符的Utf8表 在原始碼中的每一個字面值字串,都會在編譯成class檔案階段,形成標誌號(8)的常量表,在JVM載入時會為對應的常量池建立一個記憶體的資料結構,並存放在方法區中,同時JVM會自動為常量表中的字串常量的字面值在堆中建立新的String物件,然後把_String常量表的入口地址變為這個堆中String物件的直接地址(常量池解析) 原始碼中所有相同字面值的字串常量只可能建立唯一一個拘留字串的物件,實際上JVM是通過一個記錄了拘留字串引用的內部資料結構來維持這一特性,在java中可以呼叫String的intern()方法來使得一個常規字串物件成為拘留字串物件 String s = new String("Hello world");在執行指令之前JVM就會在堆中建立一個拘留字串,然後用這個拘留字串的值來初始化堆中new指令創建出來的新的String物件 String s = "Hello World";此時區域性變數儲存的是早已建立好的拘留字串的堆地址 new String建立的字串不是常量不能在編譯期確定,所以new出的String物件不放入常量池有自己的地址空間,而且由於String物件的不變性機制會使修改字串時產生大量的物件,因為每次改變字串時都會生成一個新的字串,java為了更有效的使用記憶體常量池在編譯器遇見字串時會檢查池內是否已經存在相同的String字串,找到了就會把新變數的引用指向現有的字串物件,沒找到就建立新的,所以對一個字串物件的修改會產生新的物件,之前的依然存在並等待垃圾回收。