內存分配及變量存儲位置

程序運行時,有六個地方能夠保存數據html

1.寄存器:java

這裏是最快的保存區域,由於它位於處理器內部。寄存器是內存階層中的最頂端,也是系統得到操做資料的最快速途徑。寄存器的數量有限,是根據須要由編譯器分配的。咱們對此沒有控制權限,不可能在本身的程序中找到任何存在的蹤影。數組

2.堆棧:安全

存放基本類型的數據和對象的引用(變量),但對象自己不在棧中,而在堆中。app

堆棧中的物體具備一個特性: 最後一個放入堆棧中的物體老是被最早拿出來, 這個特性一般稱爲後進先出(LIFO)隊列。 堆棧中定義了一些操做。 兩個最重要的是PUSH和POP。 PUSH操做在堆棧的頂部加入一 個元素。POP操做相反, 在堆棧頂部移去一個元素, 並將堆棧的大小減一。在常規的RAM(隨機訪問存儲器)區域 ,經過堆棧指針得到處理的直接支持。堆棧指針若向下移,會建立新的內存(PUSH),向上移會釋放那些內存(POP)。速度僅次於寄存器。建立程序時,java編譯器必須準確地知道堆棧內保存的全部數據的「長度」以及「存在時間」。這是因爲它必須生成相應的代碼,以便向上和向下移動指針。這一限制無疑影響了程序的靈活性,因此儘管有些java數據要保存在堆棧裏——特別是對象句柄,但java對象並不放到其中。jvm

3.堆:ide

存放new 產生的數據。是一種常規用途的內存池,也在RAM區域,其中保留java對象。和堆棧不一樣:「內存堆」或「堆」最吸引人的地方在於編譯器沒必要知道要從堆裏分配多少存儲空間,也沒必要知道存儲的數據要在堆裏停留多長的時間。所以,用堆保存數據時會獲得更大的靈活性。要求建立一個對象時,只需用new命令編制相碰的代碼便可。執行這些代碼時,會在堆裏自動進行數據的保存。固然,爲達到這種靈活性,必然會付出必定的代價:在堆裏分配存儲空間時會花掉更長的時間。函數

4.靜態域:測試

存放在對象中用static定義的靜態成員。靜態是指位於固定位置。程序運行期間,靜態存儲的數據隨時等候調用。用static修飾對象的特定元素是靜態的。但java對象永遠不會在靜態存儲空間。優化

5.常量池:

存放常量。常量值放置在程序代碼內部,是安全的。由於他們永遠不會改變,有的常數須要嚴格的保護,因此可考慮將它們置入只讀存儲器(ROM)

6.非RAM存儲:硬盤等永久存儲空間。若數據徹底獨立於一個程序以外,則程序不運行時仍可存在,並在程序的控制範圍以外。其中兩個最主要的例子即是「流式對象」和「固定對象」。對於流式對象,對象會變成字節流,一般會發給另外一臺機器,而對於固定對象,對象保存在磁盤中。即便程序停止運行,它們仍可保持本身的狀態不變。對於這些類型的數據存儲,一個特別有用的技藝就是它們能存在於其餘媒體中,一旦須要,甚至能將它們恢復成普通的、基於RAM的對象。

 

java內存分配中的棧(後進先出)

在函數中定義的一些基本類型的變量數據和對象的引用變量都在函數的棧內存中分配。

當代碼定義一個變量時,java就在棧中爲這個變量分配內存空間(指針向下PUSH棧頂 大小+1),當該變量退出做用域後,java會自動釋放掉爲該變量分配的內存空間(指針向上POP棧頂 大小-1),該內存空間能夠當即另作他用。棧中的數據大小和生命週期都是能夠肯定的。當沒有引用指向數據時,這個數據就會消失。

1.每一個線程包含一個棧區,棧中只保存基本數據類型的對象和自定義對象的引用(不是對象),對象都放在堆中。
2.每一個棧中的數據(原始類型和對象引用)都是私有的,其餘棧不能訪問。

3.棧分爲三個部分:基本類型變量區,執行環境上下文,操做指令區(存放操做指令)

 

java內存分配中的堆

堆內存用來存放new建立的對象和數組。在堆中分配的內存,由java虛擬機垃圾回收機制來管理。

堆中產生了一個數組或對象後,還能夠在棧中定義一個特殊的變量,讓棧中的這個變量取值等於數組或對象在堆中的首地址,棧中的這個變量就變成了數組或對象的引用變量。引用變量就至關因而爲數組或對象起的一個名稱,之後就能夠在程序中使用棧中的引用變量來訪問堆中的數組或對象。

引用變量是普通的變量,定義是在棧中分配,引用變量在程序運行到其做用域以外後被釋放。而數組和對象自己在堆中分配,即便程序運行到使用new 產生數組或對象的語句所在的代碼塊以外,數組或對象自己佔用的內存不會被釋放,數組和對象在沒有引用變量指向它的時候,才變成垃圾,不能在被使用,但仍然佔據內存空間,等待垃圾回收機制 在不定時間 回收。java比較佔內存的緣由。

1.存儲的所有是對象,每一個對象都包含一個與之對應的class的信息,class的目的是獲得操做指令。

2.jvm只有一個堆區(heap) 被全部線程共享。堆中不存放基本類型和對象引用,只存放對象自己。

實際上,棧中的變量指向堆內存中的變量,java中的指針。

 

堆與棧

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, 它是由編譯器完成的,它有利於節省空間。而一個對象引用變量修改了這個對象的內部狀態,會影響到另外一個對象引用變量。

對於成員變量和局部變量:成員變量就是方法外部,類的內部定義的變量;局部變量就是方法或語句塊內部定義的變量。局部變量必須初始化。 
形式參數是局部變量,局部變量的數據存在於棧內存中。棧內存中的局部變量隨着方法的消失而消失。 
成員變量存儲在堆中的對象裏面,由垃圾回收器負責回收。 

class BirthDate {  
         private int day;  
         private int month;  
         private int year;      
         public BirthDate(int d, int m, int y) {  
             day = d;   
             month = m;   
             year = y;  
         }  
         //省略get,set方法………  
     }  
 
     public class Test{  
         public static void main(String args[]){  
             int date = 9;  
             Test test = new Test();        
             test.change(date);   
             BirthDate d1= new BirthDate(7,7,1970);         
         }    
         public void change1(int i){  
             i = 1234;  
         }       
}

對於以上這段代碼,date爲局部變量,i,d,m,y都是形參爲局部變量,day,month,year爲成員變量。下面分析一下代碼執行時候的變化: 
1. main方法開始執行:int date = 9; 
date局部變量,基礎類型,引用和值都存在棧中。 
2. Test test = new Test(); 
test爲對象引用,存在棧中,對象(new Test())存在堆中。 
3. test.change(date); 
i爲局部變量,引用和值存在棧中。當方法change執行完成後,i就會從棧中消失。 
4. BirthDate d1= new BirthDate(7,7,1970);  
d1 爲對象引用,存在棧中,對象(new BirthDate())存在堆中,其中d,m,y爲局部變量存儲在棧中,且它們的類型爲基礎類型,所以它們的數據也存儲在棧中。 day,month,year爲成員變量,它們存儲在堆中(new BirthDate()裏面)。當BirthDate構造方法執行完以後,d,m,y將從棧中消失。
5.main方法執行完以後,date變量,test,d1引用將從棧中消失,new Test(),new BirthDate()將等待垃圾回收。

 

靜態域

方法區

1.又叫靜態區,跟堆同樣,被全部線程共享。方法區含有全部的class和static變量

2.方法區包含的都是在整個程序中永遠惟一的元素,如class static變量。

java裏面是沒有靜態變量這個概念的,不信你本身在方法裏面定義一個static int i =0;java裏只有靜態成員變量。它屬於類的屬性。至於他放在那裏?深刻jvm裏是翻譯爲靜態方法區的(應該也可叫域吧)。虛擬機的體系結構:堆,方法區,本地方法棧,pc寄存器。而方法區保存的就是一個類的模板,堆是放類的實例的。棧是通常來用來函數計算的。隨便找本計算機底層的書都知道了。棧裏的數據,函數執行完就不會存儲了。這就是爲何局部變量每一次都是同樣的。就算給他加一後,下次執行函數的時候仍是原來的樣子。

 

常量池 (constant pool)

 常量池指的是在編譯期被肯定,並被保存在已編譯的.class文件中的一些數據。

除了包含代碼中所定義的各類基本類型(如int、long等等)和對象型(如String及數組)的常量值(final)還包含一些以文本形式出現的符號引用,好比:

 類和接口的全限定名;

 字段的名稱和描述符;

 方法和名稱和描述符。

若是是編譯期已經建立好(直接用雙引號定義的)的就存儲在常量池中,若是是運行期(new出來的)才能肯定的就存儲在堆中。對於equals相等的字符串,在常量池中永遠只有一份,在堆中有多份。

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常量池問題的幾個例子

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;

例2.

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

用new String() 建立的字符串不是常量,不能在編譯期就肯定,因此new String() 建立的字符串不放入常量池中,它們有本身的地址空間。

s0仍是常量池 中"kvill」的應用,s1由於沒法在編譯期肯定,因此是運行時建立的新對象」kvill」的引用,s2由於有後半部分 new String(」ill」)因此也沒法在編譯期肯定,因此也是一個新建立對象」kvill」的應用;明白了這些也就知道爲什麼得出此結果了。

例三

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";
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

和例四中惟一不一樣的是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

 

關於String 是不可變的

經過上面例子能夠得出得知:

  String  s  =  "a" + "b" + "c";

  就等價於String s = "abc";

  String  a  =  "a";

  String  b  =  "b";

  String  c  =  "c";

  String  s  =   a  +  b  +  c;

  這個就不同了,最終結果等於:

StringBuffer temp = new StringBuffer();
temp.append(a).append(b).append(c);
String s = temp.toString();

 由上面的分析結果,可就不難推斷出String 採用鏈接運算符(+)效率低下緣由分析,形如這樣的代碼:

public class Test {
 
      public static void main(String args[]) {
 
        String s = null;
 
        for(int i = 0; i < 100; i++) {
 
            s += "a";
       }
    } 
}

 每作一次 + 就產生個StringBuilder對象,而後append後就扔掉。下次循環再到達時從新產生個StringBuilder對象,而後 append 字符串,如此循環直至結束。若是咱們直接採用 StringBuilder 對象進行 append 的話,咱們能夠節省 N - 1 次建立和銷燬對象的時間。因此對於在循環中要進行字符串鏈接的應用,通常都是用StringBuffer或StringBulider對象來進行 append操做。

  因爲String類的immutable性質,這一說又要說不少,你們只 要知道String的實例一旦生成就不會再改變了,好比說:String str=」kv」+」ill」+」 「+」ans」; 就是有4個字符串常量,首先」kv」和」ill」生成了」kvill」存在內存中,而後」kvill」又和」 」 生成 「kvill 「存在內存中,最後又和生成了」kvill ans」;並把這個字符串的地址賦給了str,就是由於String的」不可變」產生了不少臨時變量,這也就是爲何建議用StringBuffer的原 因了,由於StringBuffer是可改變的。

 

String中的final用法和理解

Java代碼

  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,數組.對象等等)但不存放對象內容

 堆中存放使用new關鍵字建立的對象.

 字符串是一個特殊包裝類,其引用是存放在棧裏的,而對象內容必須根據建立方式不一樣定(常量池和堆).有的是編譯期就已經建立好,存放在字符串常 量池中,而有的是運行時才被建立.使用new關鍵字,存放在堆中。

轉載自:https://www.cnblogs.com/protected/p/6419217.html

相關文章
相關標籤/搜索