1. 程式人生 > >Java記憶體區域——直接記憶體和執行時常量池

Java記憶體區域——直接記憶體和執行時常量池

執行時常量池是屬於方法區的一塊,class檔案中除了有類的版本、欄位、方法、介面等描述資訊以外,還有一項資訊就是常量池,那麼,這個常量池是幹什麼的呢?它就是用來存放編譯期生成的各種字面量以及符號引用這部分內容將在類載入後,進入方法區的執行時常量池中存放。舉個例子

基本資料型別抽象資料型別的引用會放到哪裡呢?我們之前所講的記憶體區域,隨著這個方法的執行,會對每一個方法建立一個棧幀,棧幀裡面有一個區域性變量表,那麼,我們所有的基本資料型別,包括抽象資料型別的引用都會放到區域性變量表中。String是一個抽象資料型別,那麼s1就是存放的引用,s1和s2這兩個引用分別存到一個區域性變量表中,那麼,“abc”到底放到哪裡了呢?“abc”是一個物件,是一個引用型別,那麼也就是說,“abc”應該按照我們的理解分配到Java堆中,如果是分配到Java堆中的話,其實我們說在建立物件例項的時候才會往堆中去分配空間

,“abc”就是s1物件的例項,我們認為,如果說它每一次都建立了例項的話,每一次建立例項,每一次都會往堆中去新開闢一塊空間來存放這個物件例項的資料

如果說,按照我們剛才說的,每一次建立一個例項,都需要在堆中開闢一塊空間,那麼,“==”比較的是地址,比如這是我們的堆記憶體

還有一塊棧記憶體

棧記憶體裡面有一塊小區域區域性變量表,區域性變量表中有兩個變數,一個是s1,一個是s2

s1和s2分別引用堆記憶體中的,我們說兩個例項,每一次都會建立一個例項

那麼,s1和s2是不相等的。Java中雖然沒有指標,但是實際上它的引用也就相當於引用了一個地址,只是對於我們開發者來講是隱式的。

這就驗證了我們這裡的說法是錯誤的

因為我們講的是常量池,而這裡說的卻是堆的問題,所以,它肯定不是在堆中去建立,那怎麼回事呢?這裡就說到了常量池,任何一個字串的建立,都會扔到常量池中常量池是在方法區中的一塊儲存空間,比如這是我們的方法區

在方法區中有一塊叫做執行時常量池

執行時常量池中其實維護了,比如說我們可以想象,它裡面有一個StringTable,字串表,那麼,它的資料型別我們可以想象成是一個HashSet,有這麼一個數據結構

來存放我們所例項化的字串物件,來一個“abc”,往這個HashSet集合中扔一個,再來一個“abc”,再往這個HashSet集合中扔一個

那麼,到這裡大家就明白了,我們知道HashSet有一個特徵,無序且不可重複,“abc”和“abc”顯然是相等的,顯然不會存在兩個,只會有一個“abc”,也就是說,只會存進去“abc”這麼一個例項,那麼,既然是只建立了一個例項,那麼,這一個例項的地址顯然是相同的,所以,s1==s2。

我們看一下s3

我們又建立了一個例項,只不過這個建立的方式是由我們手動的new建立的,那這個時候,s1和s3會不會相等呢?我們剛才說,字串都是扔到常量池中,都會扔到一個類似於HashSet這麼一個數據結構中。字串“abc”會在常量池中的HashSet中找,發現有“abc”了,就返回一個指標,讓它指向這個“abc”就可以了。那麼,也就是說s1和s3應該是相等的

為什麼s1不等於s3呢?因為,我們通過new來建立物件的話,那麼,它一定是在堆記憶體,它就不再去考慮常量池的問題了,就直接的在堆記憶體中開闢一塊空間,直接把這塊空間的地址賦值給s3,所以,new的“abc”在堆中字串物件“abc”在常量池中,顯然不是同一塊記憶體區域,那麼,記憶體地址肯定是不相同的,所以,s1==s3列印的是false。

那麼,這個intern又是一個什麼東西呢?為什麼加上它之後就變成了true?intern()這個方法,它會把堆中的這塊區域

搬到方法區中的執行時常量池中,這裡也就提到了執行時常量池。

其實我們發現,這些,就是相當於我們定義了一個字串,其實就是定義了一個字串的常量,它的值是不會改變的,那麼,它就相當於已經進入到了常量池,既然是常量了,它值是不可改變的,不可改變,那麼肯定是在我們程式碼中去宣告的,去定義的,那麼,執行時怎麼還會產生常量呢?其實

這就是執行時所產生的常量,我們稱

這種常量,我們可以稱之為它叫位元組碼常量

這個我們就可以稱之為執行時常量,其實執行時也是會產生常量的。當然了,不光使用intern()這種定義,做一個字串的拼接,拼接後就產生一個字串常量。可以認為intern()就是

把這個值拿出來,扔到

執行時常量池中,產生一個執行時常量,因為執行時常量池中已經存在“abc”了,那麼在進行s1==s3.iintern()比較的時候,其實s3.intern()拿的就是執行時常量池的中“abc”,再和s1比較,顯然就是相同的。那麼,我們可以想象一下intern()方法是如何實現的,你用Java程式碼能不能實現呢?把

這塊區域中的資料扔到

中。

其實,我們想象不到是怎麼實現的,我們來看一下intern()方法的實現

發現,我們是看不了的,我們Java程式碼確實不容易實現,使用的是一個native方法,本地方法

執行時常量池是方法區中的一部分,受到方法區記憶體的限制,如果常量池無法申請到記憶體時,同樣會丟擲OutOfMemoryError。

下面我們看直接記憶體。

直接記憶體並不是虛擬機器執行時資料區的一部分,它也不是Java虛擬機器規範中所定義的一塊記憶體區域,但是,它卻是確確實實存在的這麼一塊記憶體區域,也被我們頻繁的使用。在JDK1.4的時候,新增了一個彌補IO效能的這麼一個叫做NIO,引入了一種有緩衝區的IO方式,它可以使用native函式庫直接分配堆外記憶體,然後通過一個儲存在Java堆中的drectbytebuffer物件作為這塊記憶體的引用,進行操作,也就是說它能夠分配堆外記憶體,就不受制於Java虛擬機器記憶體的制約,其實也是為了提高效能,它雖然不受Java虛擬機器記憶體的制約,但是它會受到我們當前作業系統實體記憶體的制約,所以,當如果申請不下來記憶體,直接記憶體這一塊區域也會丟擲OutOfMemoryError,當出現這個異常的時候,我們要想到還有直接記憶體的存在,關於直接記憶體我們就說這麼多,其實它非常簡單,而且我們也不會太多的關注它,主要就是在我們像NIO中,直接分配堆外記憶體所使用的這麼一塊記憶體,就是所謂的直接記憶體。其他地方几乎是不用的。