1. 程式人生 > >JAVA中堆疊和記憶體分配原理

JAVA中堆疊和記憶體分配原理

2、Java記憶體分配中的棧 

  在函式中定義的一些基本型別的變數資料和物件的引用變數都在函式的棧記憶體中分配。 當在一段程式碼塊定義一個變數時,Java在棧中為這個變數分配記憶體空間,當該變數退出其作用域後,Java會自動釋放掉為該變數所分配的記憶體空間,該記憶體空間可以立即被另作他用。 

  Java記憶體分配中的堆 

  堆記憶體用來存放由new建立的物件和陣列。 在堆中分配的記憶體,由Java虛擬機器的自動垃圾回收器來管理。 

  在堆中產生了一個數組或物件後,還可以 在棧中定義一個特殊的變數,讓棧中這個變數的取值等於陣列或物件在堆記憶體中的首地址,棧中的這個變數就成了陣列或物件的引用變數。 引用變數就相當於是 為陣列或物件起的一個名稱,以後就可以在程式中使用棧中的引用變數來訪問堆中的陣列或物件。引用變數就相當於是為陣列或者物件起的一個名稱。 

  引用變數是普通的變數,定義時在棧中分配,引用變數在程式執行到其作用域之外後被釋放。而陣列和物件本身在堆中分配,即使程式執行到使用 new 產生陣列或者物件的語句所在的程式碼塊之外,陣列和物件本身佔據的記憶體不會被釋放
陣列和物件在沒有引用變數指向它的時候,才變為垃圾,不能在被使用,但仍然佔據記憶體空間不放,在隨後的一個不確定的時間被垃圾回收器收走(釋放掉)。這也是 Java 比較佔記憶體的原因。 

  實際上,棧中的變數指向堆記憶體中的變數,這就是Java中的指標! 常量池 (constant pool) 

  常量池指的是在編譯期被確定,並被儲存在已編譯的.class檔案中的一些資料。除了包含程式碼中所定義的各種基本型別(如int、long等等)和物件型(如String及陣列)的常量值(final)還包含一些以文字形式出現的符號引用,比如: 

  類和介面的全限定名; 

  欄位的名稱和描述符; 

  方法和名稱和描述符。 

  虛擬機器必須為每個被裝載的型別維護一個常量池。常量池就是該型別所用到常量的一個有序集和,包括直接常量(string,integer和 floating point常量)和對其他型別,欄位和方法的符號引用。 

  對於String常量,它的值是在常量池中的。而JVM中的常量池在記憶體當中是以表的形式存在的, 對於String型別,有一張固定長度的CONSTANT_String_info表用來儲存文字字串值,注意:該表只儲存文字字串值,不儲存符號引 用。說到這裡,對常量池中的字串值的儲存位置應該有一個比較明瞭的理解了。在程式執行的時候,常量池 會儲存在Method Area,而不是堆中。 

  堆與棧
 

  Java的堆是一個執行時資料區,類的(物件從中分配空間。這些物件通過new、newarray、 anewarray和multianewarray等指令建立,它們不需要程式程式碼來顯式的釋放。堆是由垃圾回收來負責的,堆的優勢是可以動態地分配記憶體 大小,生存期也不必事先告訴編譯器,因為它是在執行時動態分配記憶體的,Java的垃圾收集器會自動收走這些不再使用的資料。但缺點是,由於要在執行時動態 分配記憶體,存取速度較慢。 

  棧的優勢是,存取速度比堆要快,僅次於暫存器,棧資料可以共享。但缺點是,存在棧中的資料大小與生存期必須是確定的,缺乏靈活性。
棧中主要存放一些基本型別的變數資料(int, short, long, byte, float, double, boolean, char)和物件控制代碼(引用)。 

  棧有一個很重要的特殊性,就是存在棧中的資料可以共享
。假設我們同時定義: 

  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=4;那麼編譯器會重新搜尋棧中是否有4值,如果沒有,則將4存放進來,並令a指向4;如果已經有了,則直接將a指向這個地址。因此a值的改變不會影響 到b的值。 

  要注意這種資料的共享與兩個物件的引用同時指向一個物件的這種共享是不同的,因為這種情況a的修改並不會影響到b, 它是由編譯器完成的,它有利於節省空間。而一個物件引用變數修改了這個物件的內部狀態,會影響到另一個物件引用變數。 

  String是一個特殊的包裝類資料。可以用: 

  String str = new String("abc");   String str = "abc";  兩種的形式來建立,第一種是用new()來新建物件的,它會在存放於堆中。每呼叫一次就會建立一個新的物件。而第二種是先在棧中建立一個對String類的物件引用變數str,然後通過符號引用去字串常量池 裡找有沒有"abc",如果沒有,則將"abc"存放進字串常量池 ,並令str指向”abc”,如果已經有”abc” 則直接令str指向“abc”。 

  比較類裡面的數值是否相等時,用equals()方法;當測試兩個包裝類的引用是否指向同一個物件時,用==,下面用例子說明上面的理論。 

  String str1 = "abc"; 
  String str2 = "abc"; 
  System.out.println(str1==str2); //true 
  可以看出str1和str2是指向同一個物件的。 
  String str1 =new String ("abc"); 
  String str2 =new String ("abc"); 
  System.out.println(str1==str2); // false 
  用new的方式是生成不同的物件。每一次生成一個。 

  因此用第二種方式建立多個”abc”字串,在記憶體中 其實只存在一個物件而已. 這種寫法有利與節省記憶體空間. 同時它可以在一定程度上提高程式的執行速度,因為JVM會自動根據棧中資料的實際情況來決定是否有必要建立新物件。而對於String str = new String("abc");的程式碼,則一概在堆中建立新物件,而不管其字串值是否相等,是否有必要建立新物件,從而加重了程式的負擔。 

  另一方面, 要注意: 我們在使用諸如String str = "abc";的格式定義類時,總是想當然地認為,建立了String類的物件str。擔心陷阱!物件可能並沒有被建立!而可能只是指向一個先前已經建立的物件。只有通過new()方法才能保證每次都建立一個新的物件。 

  由於String類的immutable性質,當String變數需要經常變換 其值時,應該考慮使用StringBuffer類,以提高程式效率。 

 1. 首先String不屬於8種基本資料型別,String是一個物件。因為物件的預設值是null,所以String的預設值也是null;但它又是一種特殊的物件,有其它物件沒有的一些特性。 
  2. new String()和new String(”")都是申明一個新的空字串,是空串不是null; 
  3. String str=”kvill”;String str=new String (”kvill”)的區別 

示例: 

  String s0="kvill"; 
  String s1="kvill"; 
  String s2="kv" + "ill"; 
  System.out.println( s0==s1 ); 
  System.out.println( s0==s2 ); 
  結果為:true true 

  首先,我們要知結果為道JAVA 會確保一個字串常量只有一個拷貝。 

  因為例子中的 s0和s1中的”kvill”都是字串常量,它們在編譯期就被確定了,所以s0==s1為true;而"kv”和"ill”也都是字串常量,當一個字串由多個字串常量連線而成時,它自己肯定也是字串常量,所以s2也同樣在編譯期就被解析為一個字串常量,所以s2也是常量池中” kvill”的一個引用。所以我們得出s0==s1==s2;用new String() 建立的字串不是常量,不能在編譯期就確定,所以new String() 建立的字串不放入常量池中,它們有自己的地址空間。 

  示例: 

  String s0="kvill"; 
  String s1=new String("kvill"); 
  String s2="kv" + new String("ill"); 
  System.out.println( s0==s1 ); 
  System.out.println( s0==s2 ); 
  System.out.println( s1==s2 ); 
  結果為:false false false 

  例2中s0還是常量池 中"kvill”的應用,s1因為無法在編譯期確定,所以是執行時建立的新物件”kvill”的引用,s2因為有後半部分 new String(”ill”)所以也無法在編譯期確定,所以也是一個新建立物件”kvill”的應用;明白了這些也就知道為何得出此結果了。 

  4. String.intern(): 

  再補充介紹一點:存在於.class檔案中的常量池,在執行期被JVM裝載,並且可以擴充。String的 intern()方法就是擴充常量池的 一個方法;當一個String例項str呼叫intern()方法時,Java 查詢常量池中 是否有相同Unicode的字串常量,如果有,則返回其的引用,如果沒有,則在常 量池中增加一個Unicode等於str的字串並返回它的引用;看示例就清楚了 

  示例: 

  String s0= "kvill"; 
  String s1=new String("kvill"); 
  String s2=new String("kvill"); 
  System.out.println( s0==s1 ); 
  System.out.println( "**********" ); 
  s1.intern(); 
  s2=s2.intern(); //把常量池中"kvill"的引用賦給s2 
  System.out.println( s0==s1); 
  System.out.println( s0==s1.intern() ); 
  System.out.println( s0==s2 ); 

  結果為:false false //雖然執行了s1.intern(),但它的返回值沒有賦給s1 true //說明s1.intern()返回的是常量池中"kvill"的引用 true 

  最後我再破除一個錯誤的理解:有人說,“使用 String.intern() 方法則可以將一個 String 類的儲存到一個全域性 String 表中 ,如果具有相同值的 Unicode 字串已經在這個表中,那麼該方法返回表中已有字串的地址,如果在表中沒有相同值的字串,則將自己的地址註冊到表中”如果我把他說的這個全域性的 String 表理解為常量池的話,他的最後一句話,”如果在表中沒有相同值的字串,則將自己的地址註冊到表中”是錯的: 

  示例: 

  String s1=new String("kvill"); 
  String s2=s1.intern(); 
  System.out.println( s1==s1.intern() ); 
  System.out.println( s1+" "+s2 ); 
  System.out.println( s2==s1.intern() ); 
  結果:false kvill kvill true 

  在這個類中我們沒有聲名一個”kvill”常量,所以常量池中一開始是沒有”kvill”的,當我們呼叫s1.intern()後就在常量池中新添加了一 個”kvill”常量,原來的不在常量池中的”kvill”仍然存在,也就不是“將自己的地址註冊到常量池中”了。 

  s1==s1.intern() 為false說明原來的”kvill”仍然存在;s2現在為常量池中”kvill”的地址,所以有s2==s1.intern()為true。 

  5. 關於equals()和==: 

  這個對於String簡單來說就是比較兩字串的Unicode序列是否相當,如果相等返回true;而==是 比較兩字串的地址是否相同,也就是是否是同一個字串的引用。 

  6. 關於String是不可變的 

  這一說又要說很多,大家只 要知道String的例項一旦生成就不會再改變了,比如說:String str=”kv”+”ill”+” “+”ans”; 就是有4個字串常量,首先”kv”和”ill”生成了”kvill”存在記憶體中,然後”kvill”又和” ” 生成 “kvill “存在記憶體中,最後又和生成了”kvill ans”;並把這個字串的地址賦給了str,就是因為String的”不可變”產生了很多臨時變數,這也就是為什麼建議用StringBuffer的原 因了,因為StringBuffer是可改變的。 

  下面是一些String相關的常見問題: 

  String中的final用法和理解 
  final StringBuffer a = new StringBuffer("111"); 
  final StringBuffer b = new StringBuffer("222"); 
  a=b;//此句編譯不通過  final StringBuffer a = new StringBuffer("111"); 
  a.append("222");// 編譯通過 

  可見,final只對引用的"值"(即記憶體地址)有效,它迫使引用只能指向初始指向的那個物件,改變它的指向會導致編譯期錯誤。至於它所指向的物件 的變化,final是不負責的。 
  

String常量池問題的幾個例子 

  下面是幾個常見例子的比較分析和理解: 

  String a = "a1"; 
  String b = "a" + 1; 
  System.out.println((a == b)); //result = true 
  String a = "atrue"; 
  String b = "a" + "true"; 
  System.out.println((a == b)); //result = true 
  String a = "a3.4"; 
  String b = "a" + 3.4; 
  System.out.println((a == b)); //result = true 

  分析:JVM對於字串常量的"+"號連線,將程式編譯期,JVM就將常量字串的"+"連線優化為連線後的值,拿"a" + 1來說,經編譯器優化後在class中就已經是a1。在編譯期其字串常量的值就確定下來,故上面程式最終的結果都為true。 

  String a = "ab"; 
  String bb = "b"; 
  String b = "a" + bb; 
  System.out.println((a == b)); //result = false 

  分析:JVM對於字串引用,由於在字串的"+"連線中,有字串引用存在,而引用的值在程式編譯期是無法確定的,即"a" + bb無法被編譯器優化,只有在程式執行期來動態分配並將連線後的新地址賦給b。所以上面程式的結果也就為false。 

  String a = "ab"; 
  final String bb = "b"; 
  String b = "a" + bb; 
  System.out.println((a == b)); //result = true 

  分析:和[3]中唯一不同的是bb字串加了final修飾,對於final修飾的變數,它在編譯時被解析為常量值的一個本地拷貝儲存到自己的常量池中或嵌入到它的位元組碼流中。所以此時的"a" + bb和"a" + "b"效果是一樣的。故上面程式的結果為true。 

  String a = "ab"; 
  final String bb = getBB(); 
  String b = "a" + bb; 
  System.out.println((a == b)); 
     //result = false 
  private static String getBB() { 
       return "b"; 
  } 

  分析:JVM對於字串引用bb,它的值在編譯期無法確定,只有在程式執行期呼叫方法後,將方法的返回值和"a"來動態連線並分配地址為b,故上面 程式的結果為false。 

  通過上面4個例子可以得出得知: 
  String s = "a" + "b" + "c";  就等價於String s = "abc";

相關推薦

JAVA堆疊記憶體分配原理

2、Java記憶體分配中的棧   在函式中定義的一些基本型別的變數資料和物件的引用變數都在函式的棧記憶體中分配。 當在一段程式碼塊定義一個變數時,Java在棧中為這個變數分配記憶體空間,當該變數退出其作用域後,Java會自動釋放掉為該變數所分配的記憶體空間,該記憶體空間可以立即被另作他用。    Java記憶

淺談 java堆疊記憶體分配原理

在java中我們把java記憶體分為兩種一種是棧記憶體,一種則是堆記憶體 1.在談java堆疊知識之前我們先來看看java虛擬機器的自動垃圾回收機制 引用變數是普通的變數,定義時在棧中分配,引用變數在程式執行到其作用域之外後被釋放。而陣列和物件本身在堆中

堆疊記憶體分配

一:記憶體管理概述: 如圖一所示,在計算機中,主要分為以上儲存區域中,分別是:硬碟、記憶體、高階快取 和暫存器。執行程式後,他們的執行速率自下而上(圖一)加快,與之相應的造價越高,其中,硬碟的執行效率最慢,暫存器的效率最快。在這幾個區域裡,今天重點介紹一下

Java的陣列記憶體分配

理解陣列 概念:陣列是儲存同一種資料型別多個元素的集合。也可以看成是一個容器。 陣列既可以儲存基本資料型別,也可以儲存引用資料型別,只要所有的陣列元素具有相同的資料型別即可 定義陣列的方法: ①:type[] arrayName;(推薦使用這種方式) ②:ty

Java的堆記憶體記憶體

Java中的堆記憶體和棧記憶體 本文主要討論作者對於Java記憶體中堆疊的理解. Oralce官方對於棧(stack)的解釋: Each Java Virtual Machine thread has a private Java Virtual Machine stack, created at

Java虛擬機器】垃圾收集器記憶體分配策略

垃圾收集器和記憶體分配策略 垃圾收集器 Serial 收集器 ParNew 收集器 Parallel Scavenge 收集器 Serial Old 收集器 Parallel Old 收集器 CMS 收集器(C

javaHashMap、CurrentHashMap 工作原理&&HashTable、HashSet的區別

HashMap和HashTable的區別 HashMap儲存的是鍵值對(接受null鍵值對),不支援synchronized,速度很快; HashTable不接受null鍵值對,可同步(Synchronized) 雖然HashMap是非Synchronized,但collection

javaString new String還有物件的String字串在記憶體的儲存

一直以來,所有人都說,java中的String型別是不可變的,可是為什麼不可變確很少有人說的透徹,String和new String的區別,物件中的String和直接定義一個String是否有區別,一直都是一知半解。看了很多文件都是各種猜測,沒有具體程式碼來

Java分配原理簡析以及靜態非靜態關係簡述

1.棧:函式中定義的基本型別變數以及物件的引用變數都是存在於棧中,當定義了一個變數後,就會在棧中為其分配記憶體空間,當這個變數的作用域結束後,就會釋放此變數的記憶體空間,以便另作他用。棧的優勢是,存取速度比堆要快,僅次於直接位於CPU中的暫存器。但存在棧中的資料

Java關鍵字new-----物件的記憶體分配原理

一、關鍵字new概述         "new"可以說是Java開發者最常用的關鍵字,我們使用new建立物件,使用new並通過類載入器來例項化任何我們需要的東西,但你是否深入瞭解過new在編譯的瞬間都做了什麼?         在Java中使用new關鍵字建立物件變得很容

JAVA陣列的記憶體(棧堆)

JAVA對記憶體空間的劃分 五部分:棧 堆 方法區 本地方法區 暫存器 今天主要談棧和堆 棧記憶體:儲存的都是區域性變數。 只要是在方法中定義的變數都是區域性變數,一旦變數的生命週期結束,該變數就被釋放。 (壓棧彈棧 balabalabala) 堆記

java垃圾收集器記憶體分配回收策略

概述GC要完成3件事:哪些記憶體需要回收?什麼時候回收?如何回收?Java記憶體執行時區域的各部分,其中程式計數器、虛擬機器棧、本地方法棧3個區域隨執行緒而生,隨執行緒而滅;棧中的棧幀隨著方法的進入和退出而有條不紊地執行著入棧和出棧操作。每一個棧幀中分配多少記憶體基本上是在類

java記憶體模型記憶體分配

1.什麼是jvm?(1)jvm是一種用於計算裝置的規範,它是一個虛構出來的機器,是通過在實際的計算機上模擬模擬各種功能實現的。(2)jvm包含一套位元組碼指令集,一組暫存器,一個棧,一個垃圾回收堆和一個儲存方法域。(3)JVM遮蔽了與具體作業系統平臺相關的資訊,使Java程式只需生成在Java虛擬機器上執行的

java虛擬機器記憶體分配原理概述

本文主要介紹在應用發起記憶體申請,到作業系統最終分配記憶體,採用了那些途徑和方法,並比較各種方法的優劣以及使用過程中應該注意那些點。 注意本文都是概述,如想詳細瞭解,需單獨詳細瞭解每一部分內容 1、應用在那些情況下發起記憶體申請 2、記憶體發起申請的步驟(

使用 Memory Profiler 檢視 Java記憶體分配

Memory Profiler 是 Android Profiler 中的一個元件,可幫助您識別導致應用卡頓、凍結甚至崩潰的記憶體洩漏和流失。 它顯示一個應用記憶體使用量的實時圖表,讓您可以捕獲堆轉儲、強制執行垃圾回收以及跟蹤記憶體分配。 要開啟 Memory Profil

Java記憶體區域劃分、記憶體分配原理(基於jdk1.7 源自 《深入理解java虛擬機器》)

執行時資料區域         Java虛擬機器在執行Java的過程中會把管理的記憶體劃分為若干個不同的資料區域。這些區域有各自的用途,以及建立和銷燬的時間,有的區域隨著虛擬機器程序的啟動而存在,而有的區域則依賴執行緒的啟動和結束而建立和銷燬。        Java虛擬機

JVM面試題整理-Java記憶體區域與記憶體溢位異常、垃圾收集器記憶體分配策略

1、Java虛擬機器記憶體(執行時資料區域)的劃分,每個區域的功能 關於JVM 執行時記憶體劃分的例項參考: http://www.cnblogs.com/hellocsl/p/3969768.html?utm_source=tuicool&

JavaIntegerint比較大小出現的錯誤

最好 裏的 pan 轉換 als 範圍 urn 返回 錯誤 Java在某一處維護著一個常量池,(我記得)在小於128的範圍內,直接用 1 Integer i = 100; 2 int j = 100; 3 return i == j;//true 這裏返回的是true.

javaArrayListLinkedList區別

插入 list 新的 查找 arr tro 基於 列表 時間復雜度 ArrayList和LinkedList最主要的區別是基於不同數據結構 ArrayList是基於動態數組的數據結構,LinkedList基於鏈表的數據結構,針對這點,從時間復雜度和空間復雜度來看主要區別:

java棧的區別

mem 線程 所有 生成 werror 空間 調用 訪問 指向 01,各司其職;         棧內存用來存儲局部變量和方法的調用,         而堆內存用來存儲java中的對象,無論是成員變量,局部變量,還是類變量         他們指向的對象都存儲在堆內存中。