第二章 Java內存區域與內存溢出異常(1)

JAVA中常遇到的幾種常量池的區別

1. Class文件常量池

Class文件中除了有類的版本信息,字段,方法,接口等描述信息外,還有一部分叫Class文件常量池,這個常量池可以理解為Class文件中的資源倉庫,它當中主要存放兩大類常量:字面量和符號引用

字面量:如文本字符串,"aaa"; 聲明為final類型的常量值等等
符號引用:又分為三類常量
1)類和接口的全限定名
2)字段的名稱和描述符
3)方法的名稱和描述符

2. 運行時常量池

運行時常量池是方法區的一部分,Class文件常量池中的內容在編譯時就產生了,而在類加載后,這部分內容會存在運行時常量池中,另外,由符號引用轉變成的直接引用也會存在運行時常量池中。
運行時常量池相對于Class文件常量池的一個重要特征是具備動態性,也就是常量并不一定在編譯時產生,運行時也可能將新的常量放入常量池中。

3. 字符串池

這是一個比較難懂的概念,在工作中,String類是我們使用頻率非常高的一種對象類型。JVM為了提升性能和減少內存開銷,避免字符串的重復創建,其維護了一塊特殊的內存空間,即字符串池(String Pool)。這部分內存之前是在方法區,jdk1.8之后已經移除了方法區,轉而替代為Metaspace區,那么這個字符串池應該是被劃到這個Metaspace中了吧(有疑問,還沒弄明白)。

我們知道,在Java中有兩種創建字符串對象的方式:
1)采用字面值的方式賦值
2)采用new關鍵字新建一個字符串對象。
這兩種方式在性能和內存占用方面存在著差別

方式一:采用字面值的方式賦值,例如:

String a = "aaa";
String b = "aaa";
System.out.println(a == b)

我們來分析一下過程,JVM首先會去字符串池中查找是否存在"aaa"這個對象,如果不存在,則在字符串池中創建"aaa"這個對象,然后將池中"aaa"這個對象的引用地址返回給字符串常量a,這樣a會指向池中"aaa"這個字符串對象;如果存在,則不創建任何對象,直接將池中"aaa"這個對象的地址返回,賦給字符串常量b。所以a==b返回值是true,因為二均指向了字符串池中的"aaa".

方式二:采用new關鍵字新建一個字符串對象,例如:

String a = new String("aaa");
String b = new String("aaa");
System.out.println(a == b);

采用new關鍵字,JVM會先從常量池中查看有無"aaa"字符串,有的話就拷貝一份到新new出來的堆內存中,返回的是堆內存的地址;如果沒有的話,直接在堆中new出來一塊空間存放"aaa"的值,同樣返回的是堆內存的地址,那么問題來了

這個時候這個堆內存的"aaa"是否會也在字符串池中創建一份呢?

這個問題在網上爭議很大,有的認為這個時候也會在字符串池中創建一份,這個說法我不太認同,因為這樣的話豈不是造成了堆和字符串池的完全重復?也就是不管字符串池中有沒有"aaa",只要我是new,那都會在堆和字符串池中同時存在"aaa".這樣不就造成了內存的浪費嗎?還有一種說法是,如果字符串池中沒有"aaa",那先在堆中創造出"aaa",如果需要往字符串池中加入"aaa"的話,就調用String的intern方法。我個人比較認同這種說法。

關于intern方法

intern方法使用:一個初始為空的字符串池,它由類String獨自維護。當調用 intern方法時,如果池已經包含一個等于此String對象的字符串(用equals(oject)方法確定),則返回池中的字符串。否則,將此String對象添加到池中,并返回此String對象的引用。 對于任意兩個字符串s和t,當且僅當s.equals(t)為true時,s.instan() == t.instan才為true。所有字面值字符串和字符串賦值常量表達式都使用 intern方法進行操作。

下面看一些經常出現的例子
String s1 = "Hello";
String s2 = "Hello";
String s3 = "Hel" + "lo";
String s4 = "Hel" + new String("lo");
String s5 = new String("Hello");
String s6 = s5.intern();
String s7 = "H";
String s8 = "ello";
String s9 = s7 + s8;
           
System.out.println(s1 == s2);  // true
System.out.println(s1 == s3);  // true
System.out.println(s1 == s4);  // false
System.out.println(s1 == s9);  // false
System.out.println(s4 == s5);  // false
System.out.println(s1 == s6);  // true

首先說明一點,在java 中,直接使用==操作符,比較的是兩個字符串的引用地址,并不是比較內容,比較內容請用String.equals()。

s1 == s2這個非常好理解,s1、s2在賦值時,均使用的字符串字面量,說白話點,就是直接把字符串寫死,在編譯期間,這種字面量會直接放入class文件的常量池中,從而實現復用,載入運行時常量池后,s1、s2指向的是同一個內存地址,所以相等。

s1 == s3這個地方有個坑,s3雖然是動態拼接出來的字符串,但是所有參與拼接的部分都是已知的字面量,在編譯期間,這種拼接會被優化,編譯器直接幫你拼好,因此String s3 = "Hel" + "lo";在class文件中被優化成String s3 = "Hello";,所以s1 == s3成立。

s1 == s4當然不相等,s4雖然也是拼接出來的,但new String("lo")這部分不是已知字面量,是一個不可預料的部分,編譯器不會優化,必須等到運行時才可以確定結果,結合字符串不變定理,鬼知道s4被分配到哪去了,所以地址肯定不同。

s1 == s9也不相等,道理差不多,雖然s7、s8在賦值的時候使用的字符串字面量,但是拼接成s9的時候,s7、s8作為兩個變量,都是不可預料的,編譯器畢竟是編譯器,不可能當解釋器用,所以不做優化,等到運行時,s7、s8拼接成的新字符串,在堆中地址不確定,不可能與方法區常量池中的s1地址相同。

s4 == s5已經不用解釋了,絕對不相等,二者都在堆中,但地址不同。

s1 == s6這兩個相等完全歸功于intern方法,s5在堆中,內容為Hello ,intern方法會嘗試將Hello字符串添加到常量池中,并返回其在常量池中的地址,因為常量池中已經有了Hello字符串,所以intern方法直接返回地址;而s1在編譯期就已經指向常量池了,因此s1和s6指向同一地址,相等。

這只是讀書筆記,大多內容都是來自于其他前輩的帖子和《深入理解Java虛擬機》這本書,所有來源均列出,供大家閱讀

Java字符串池和字符串堆的內存分配
String放入運行時常量池的時機與String.intern()方法解惑
Java 6,7,8 中的 String.intern – 字符串池
Java中的字符串常量池與Java中的堆和棧的區別
Java字符串池(String Pool)深度解析
觸摸java常量池
Java中幾種常量池的區分

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,963評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,348評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,083評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,706評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,442評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,802評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,795評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,983評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,542評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,287評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,486評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,030評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,710評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,116評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,412評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,224評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,462評論 2 378

推薦閱讀更多精彩內容

  • 1.概述 對于 Java 的開發者來說,在虛擬機的自動內存管理機制的幫助下,不再需要為每一個 new 操作去寫配對...
    SawyerZh閱讀 3,188評論 3 81
  • Java內存分配與管理是Java的核心技術之一,一般Java在內存分配時會涉及到以下區域: 寄存器:我們在程序中無...
    yekai閱讀 337評論 0 3
  • ??需要說明的一點是,這篇文章是以《深入理解Java虛擬機》第二版這本書為基礎的,這里假設大家已經了解了JVM的運...
    Geeks_Liu閱讀 14,060評論 5 44
  • 作家穆紫荊,德籍。1962年生於上海。 外公(中國語言學家、文學家、文學批評史家)郭紹虞。 1984年畢業於復旦大...
    沈香閱讀 503評論 0 0
  • 終于補完整了,可以正式寫自己了! 悠然自在地做事,莫理旁邊人的是與非!今天做得不錯! 中午偶然和原來的老同事聊天,...
    兮兮0225閱讀 112評論 0 0