第二章 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中幾種常量池的區分
相關文章
- JAVA記憶體區域與記憶體溢位異常Java記憶體溢位
- JVM(2)-Java記憶體區域與記憶體溢位異常JVMJava記憶體溢位
- Java記憶體區域與記憶體溢位異常(JVM學習系列1)Java記憶體溢位JVM
- Java記憶體區域與記憶體溢位異常 - 執行時資料區Java記憶體溢位
- JVM學習-02-Java記憶體區域與記憶體溢位異常JVMJava記憶體溢位
- 深入理解Java虛擬機器-Java記憶體區域與記憶體溢位異常Java虛擬機記憶體溢位
- 深入理解JVM讀書筆記一: Java記憶體區域與記憶體溢位異常JVM筆記Java記憶體溢位
- Java虛擬機器01——Java記憶體資料區域和記憶體溢位異常Java虛擬機記憶體溢位
- 深入理解JVM之記憶體區域與記憶體溢位JVM記憶體溢位
- Java記憶體溢位(OOM)異常完全指南Java記憶體溢位OOM
- 【深入Java虛擬機器】之一:Java記憶體區域與記憶體溢位Java虛擬機記憶體溢位
- Java棧溢位|記憶體洩漏|記憶體溢位Java記憶體溢位
- Java 常見記憶體溢位異常與程式碼實現Java記憶體溢位
- Java記憶體溢位Java記憶體溢位
- java記憶體溢位和記憶體洩漏的區別Java記憶體溢位
- JVM記憶體區域以及各區域的記憶體溢位異常,記憶體分代策略,垃圾收集演算法,各種垃圾收集器JVM記憶體溢位演算法
- JVM——記憶體洩漏與記憶體溢位JVM記憶體溢位
- java 程式記憶體溢位Java記憶體溢位
- 深入理解JVM虛擬機器-JVM記憶體區域與記憶體溢位JVM虛擬機記憶體溢位
- 記憶體溢位記憶體溢位
- JVM系列之實戰記憶體溢位異常JVM記憶體溢位
- Java記憶體區域和記憶體模型Java記憶體模型
- [Java基礎]記憶體洩漏和記憶體溢位Java記憶體溢位
- 記憶體溢位和記憶體洩露記憶體溢位記憶體洩露
- 記憶體洩漏和記憶體溢位記憶體溢位
- Java記憶體溢位情況Java記憶體溢位
- 記憶體洩漏與記憶體溢位神比較記憶體溢位
- Java記憶體區域Java記憶體
- 【記憶體洩漏和記憶體溢位】JavaScript之深入淺出理解記憶體洩漏和記憶體溢位記憶體溢位JavaScript
- JBOSS記憶體溢位記憶體溢位
- 【轉】java中的記憶體溢位和記憶體洩漏Java記憶體溢位
- JVM面試問題系列:深入詳解JVM 記憶體區域及記憶體溢位分析JVM面試記憶體溢位
- Java記憶體區域與分配策略Java記憶體
- 異常、堆記憶體溢位、OOM的幾種情況記憶體溢位OOM
- JavaScript之記憶體溢位和記憶體洩漏JavaScript記憶體溢位
- WebLogic: 記憶體溢位Web記憶體溢位
- 記憶體溢位問題記憶體溢位
- 記憶體溢位的分析記憶體溢位