了解棧內存堆內存


由於 java 有垃圾回收機制,所以往往不太會去關注棧堆的內存分配問題,直至OOM

一、了解棧堆概念

1、堆【存儲對象創建實例】

  1. 程序開始運行時,JVM從OS獲取一些內存,部分是堆內存。堆內存通常在存儲地址的底層,向上排列。
  2. 堆是一個"運行時"數據區,類實例化的對象就是從堆上去分配空間的;
  3. 在堆上分配空間是通過"new"等指令建立的,堆是動態分配的內存大小,生存期也不必事先告訴編譯器;
  4. 與C++不同的是,Java自動管理堆和棧,垃圾回收器可以自動回收不再使用的堆內存;
  5. 缺點是,由於要在運行時動態分配內存,所以內存的存取速度較慢。

2、棧【存放基本類型和引用類型】

  1. 先進后出的數據結構,通常用於保存方法中的參數,局部變量;
  2. 在java中,所有基本類型(short,int, long, byte, float, double,boolean, char)和引用類型的變量都在棧中存儲;
  3. 棧中數據的生存空間一般在當前scopes內(由{…}括起來的區域;
  4. 棧的存取速度比堆要快,僅次於直接位於CPU中的寄存器;
  5. 棧中的數據可以共享,多個引用可以指向同一個地址;
  6. 缺點是,棧的數據大小與生存期必須是確定的,缺乏靈活性。

3、總結

  1. Java堆內存是操作系統分配給JVM的內存的一部分。
  2. 當我們創建對象時,它們存儲在Java堆內存中。
  3. 你可以通過用JVM的命令行選項 -Xms, -Xmx, -Xmn來調整Java堆空間的大小。
  4. 棧中存放的基本類型數據:short,int, long, byte, float, double,boolean, char
  5. 只要采用 new 的形式創建對象,那么就存儲在 java 堆內存中。

二、棧中的數據存儲

1、基本類型數據存儲

int a = 3;
int b = 3;

編譯器先處理int a = 3;
首先它會在棧中創建一個變量為a的引用,然后查找有沒有字面值為3的地址,沒找到,就開辟一個存放3這個字面值的地址,
然后將a指向3的地址。接着處理int b = 3;在創建完b的引用變量后,由於在棧中已經有3這個字面值,便將b直接指向3的地址。
這樣,就出現了a與b同時均指向3的情況。

所以 a == b true

2、包裝類數據存儲

如Integer, Double, String等將相應的基本數據類型包裝起來的類。
這些類數據全部存在於堆中,Java用new()語句來顯示地告訴編譯器,在運行時才根據需要動態創建,因此比較靈活,但缺點是要占用更多的時間。

以String為例

String是一個特殊的包裝類數據。即可以用String str = new String("abc");的形式來創建,也可以用String str = "abc";的形式來創建。
前者是規范的類的創建過程,即在Java中,一切都是對象,而對象是類的實例,全部通過new()的形式來創建。

那為什么在String str = "abc";中,並沒有通過new()來創建實例,是不是違反了上述原則?其實沒有。

關於String str = "abc"的內部工作。Java內部將此語句轉化為以下幾個步驟:

  • a、先定義一個名為str的對String類的對象引用變量:String str;
  • b、在棧中查找有沒有存放值為"abc"的地址,如果沒有,則開辟一個存放字面值為"abc"的地址,接着創建一個新的String類的對象O,並將O的字符串值指向這個地址,而且在棧中這個地址旁邊記下這個引用的對象O。如果已經有了值為"abc"的地址,則查找對象O,並返回O的地址。
  • c、將str指向對象O的地址。
     值得注意的是,通常String類中字符串值都是直接存值的。但像String str = "abc";這種場合下,其字符串值卻是保存了一個指向存在棧中數據的引用(即:String str = "abc";既有棧存儲,又有堆存儲)。

為了更好地說明這個問題,我們可以通過以下的幾個代碼進行驗證。

String str1 = "abc";
String str2 = "abc";
System.out.println(str1==str2); //true

只有在兩個引用都指向了同一個對象時才返回真值。str1與str2是否都指向了同一個對象)

結果說明,JVM創建了兩個引用str1和str2,但只創建了一個對象,而且兩個引用都指向了這個對象。

3、總結

(1). 我們在使用諸如String str = "abc";的格式定義類時,總是想當然地認為,我們創建了String類的對象str。

擔心陷阱!對象可能並沒有被創建!唯一可以肯定的是,指向 String類的引用被創建了。
至於這個引用到底是否指向了一個新的對象,必須根據上下文來考慮,除非你通過new()方法來顯要地創建一個新的對象。
因此,更為准確的說法是,我們創建了一個指向String類的對象的引用變量str,這個對象引用變量指向了某個值為"abc"的String類。
清醒地認識到這一點對排除程序中難以發現的bug是很有幫助的。

(2). 使用String str = "abc";的方式,可以在一定程度上提高程序的運行速度,因為JVM會自動根據棧中數據的實際情況來決定是否有必要創建新對象。
而對於String str = new String("abc");的代碼,則一概在堆中創建新對象,而不管其字符串值是否相等,是否有必要創建新對象,從而加重了程序的負擔。

(3). 由於String類的immutable性質(因為包裝類的值不可修改),當String變量需要經常變換其值時,應該考慮使用StringBuffer類,以提高程序效率。

4、實例

String s = new String("abc") 創建了幾個對象?

首先我們要明白兩個概念,引用變量和對象,對象一般通過new在堆中創建,s只是一個引用變量。

答案2個。

5、知識點

我們可以用==判斷一下兩個引用變量是否指向了一個地址即一個對象


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM