堆棧這個概念存在於數據結構中,也存在於jvm虛擬機中,在這兩個環境中是大相徑庭的意思。 java
在數據結構中,堆棧是:堆 和棧兩種數據結構,堆是徹底二叉樹,堆中各元素是有序的。在這個二叉樹中全部的雙親節點和孩子節點存在着大小關係,如全部的雙親節點都大於孩子節點則 爲大頭堆,若是全部的雙親節點都小於其孩子節點說明這是一個小頭堆,建堆的過程就是一個排序的過程,堆得查詢效率也很高。棧是一種先進後出的線性表。 程序員
在jvm虛擬機中得堆棧對應內存的不一樣區域,和數據結構中所說的堆棧是兩碼事。 數組
下面介紹jvm中得堆棧以及jvm內存分配: 緩存
JVM的體系結構以下: 數據結構
以下圖所示,JVM的體系結構包含幾個主要的子系統和內存區: 多線程
類裝載子系統 ,負責把類從文件系統中裝入內存 jvm
GC子系統 ,垃圾收集器的主要工做室自動回收再也不運行的程序引用對象所佔用的內存,此外,它還可能負責那些還在使用的對象,以減小的堆碎片。 ide
內存區 ,用於存儲字節碼,程序運行時建立的對象,傳遞給方法的參數,返回值,局部變量和中間計算結果。 函數
2. Java的內存分配 性能
在Java程序運行過程當中,JVM定義了各類區域用於存儲運行時數據。其中的有些數據區域在JVM啓動時建立,並只在JVM退出時銷燬。其它的數據區域與每一個線程相關。這些數據區域,在線程建立時建立,在線程退出時銷燬。
2.1 程序計數器寄存器(The pc Register)
JVM支持多個線程同時運行。每一個JVM都有本身的程序計數器。在任何一個點,每一個JVM線程執行單個方法的代碼,這個方法是線程的當前方法。若是方法不是native的,程序計數器寄存器包含了當前執行的JVM指令的地址,若是方法是 native的,程序計數器寄存器的值不會被定義。 JVM的程序計數器寄存器的寬度足夠保證能夠持有一個返回地址或者native的指針。
2.2 棧
1)棧與線程
JVM是基於棧的虛擬機.JVM爲每一個新建立的線程都分配一個棧.也就是說,對於一個Java程序來講,它的運行就是經過對棧的操做來完成的。棧以幀爲單位保存線程的狀態。JVM對棧只進行兩種操做:以幀爲單位的壓棧和出棧操做。
咱們知道,某個線程正在執行的方法稱爲此線程的當前方法.咱們可能不知道,當前方法使用的幀稱爲當前幀。當線程激活一個Java方法,JVM就會在線程的 Java堆棧裏新壓入一個幀。這個幀天然成爲了當前幀.在此方法執行期間,這個幀將用來保存參數,局部變量,中間計算過程和其餘數據.這個幀在這裏和編譯原理中的活動紀錄的概念是差很少的.
從Java的這種分配機制來看,堆棧又能夠這樣理解:棧(Stack)是操做系統在創建某個進程時或者線程(在支持多線程的操做系統中是線程)爲這個線程創建的存儲區域,該區域具備先進後出的特性。
2)棧中的方法調用
嵌套方法的出棧和入棧示意圖:
上圖中描述了嵌套方法時,stack的內存分配圖,由上面能夠知道,當嵌套方法調用時,嵌套越深,stack的內存就越晚才能釋放,所以,在實際開發過程當中,不推薦你們使用遞歸來進行方法的調用,遞歸很容易致使stack flow。
非嵌套方法的出棧入棧過程:
2.3 堆
每個Java應用都惟一對應一個JVM實例,每個實例惟一對應一個堆。應用程序在運行中所建立的全部類實例或數組都放在這個堆中,並由應用全部的線程共享.跟C/C++不一樣,Java中分配堆內存是自動初始化的。Java中全部對象的存儲空間都是在堆中分配的,可是這個對象的引用倒是在堆棧中分配,也就是說在創建一個對象時從兩個地方都分配內存,在堆中分配的內存實際創建這個對象,而在堆棧中分配的內存只是一個指向這個堆對象的指針(引用)而已。
2.4 堆和棧的區別
【下面的部分屬於摘抄,描述比較好】
1. 棧(stack)與堆(heap)都是Java用來在Ram中存放數據的地方 。與C++不一樣,Java自動管理棧和堆,程序員不能直接地設置棧或堆。
2. 棧的優點是,存取速度比堆要快 ,僅次於直接位於CPU中的寄存器。但缺點是,存在棧中的數據大小與生存期必須是肯定的,缺少靈活性。另外,棧數據能夠共享,詳見第3點。堆的優點是能夠動態地分配內存大小,生存期也沒必要事先告訴編譯器,Java的垃圾收集器會自動收走這些再也不使用的數據。但缺點是,因爲要在運行時動態分配內存,存取速度較慢。
3. Java中的數據類型有兩種:
一種是基本類型(primitive types), 共有8種,即int, short, long, byte, float, double, boolean, char(注意,並無string的基本類型)。這種類型的定義是經過諸如int a = 3; long b = 255L;的形式來定義的,稱爲自動變量。值得注意的是,自動變量存的是字面值,不是類的實例,即不是類的引用,這裏並無類的存在。如int a = 3; 這裏的a是一個指向int類型的引用,指向3這個字面值。這些字面值的數據,因爲大小可知,生存期可知(這些字面值固定定義在某個程序塊裏面,程序塊退出後,字段值就消失了),出於追求速度的緣由,就存在於棧中。
另外,棧有一個很重要的特殊性,就是存在棧中的數據能夠共享。假設咱們同時定義:
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的值後,再令a=4;那麼,b不會等於4,仍是等於3。在編譯器內部,遇到a=4;時,它就會從新搜索棧中是否有4的字面值,若是沒有,從新開闢地址存放4的值;若是已經有了,則直接將a指向這個地址。所以a值的改變不會影響到b的值。
另外一種是包裝類數據 ,如Integer, String, Double等將相應的基本數據類型包裝起來的類。這些類數據所有存在於堆中,Java用new()語句來顯示地告訴編譯器,在運行時才根據須要動態建立,所以比較靈活,但缺點是要佔用更多的時間。
4. String是一個特殊的包裝類數據 。便可以用String str = new String("abc");的形式來建立,也能夠用String str = "abc";的形式來建立(做爲對比,在JDK 5.0以前,你從未見過Integer i = 3;的表達式,由於類與字面值是不能通用的,除了String。而在JDK 5.0中,這種表達式是能夠的!由於編譯器在後臺進行Integer i = new Integer(3)的轉換)。前者是規範的類的建立過程,即在Java中,一切都是對象,而對象是類的實例,所有經過new()的形式來建立。Java中的有些類,如DateFormat類,能夠經過該類的getInstance()方法來返回一個新建立的類,彷佛違反了此原則。其實否則。該類運用了單例模式來返回類的實例,只不過這個實例是在該類內部經過new()來建立的,而getInstance()向外部隱藏了此細節。那爲何在String str = "abc";中,並無經過new()來建立實例,是否是違反了上述原則?其實沒有。
5. 關於String str = "abc"的內部工做。
Java內部將此語句轉化爲如下幾個步驟:
(1)先定義一個名爲str的對String類的對象引用變量:String str;
(2)在棧中查找有沒有存放值爲"abc"的地址,若是沒有,則開闢一個存放字面值爲"abc"的地址,接着建立一個新的String類的對象o,並將o的字符串值指向這個地址,並且在棧中這個地址旁邊記下這個引用的對象o。若是已經有了值爲"abc"的地址,則查找對象o,並返回o的地址。
(3)將str指向對象o的地址。
值得注意的是,通常String類中字符串值都是直接存值的。但像String str = "abc";這種場合下,其字符串值倒是保存了一個指向存在棧中數據的引用!
爲了更好地說明這個問題,咱們能夠經過如下的幾個代碼進行驗證。
String str1 = "abc";
String str2 = "abc";
System.out.println(str1==str2); //true
注意,咱們這裏並不用str1.equals(str2);的方式,由於這將比較兩個字符串的值是否相等。「==」號,根據JDK的說明,只有在兩個引用都指向了同一個對象時才返回真值。而咱們在這裏要看的是,str1與str2是否都指向了同一個對象。結果說明,JVM建立了兩個引用str1和str2,但只建立了一個對象,並且兩個引用都指向了這個對象。
咱們再來更進一步,將以上代碼改爲:
String str1 = "abc";
String str2 = "abc";
str1 = "bcd";
System.out.println(str1 + "," + str2); //bcd, abc
System.out.println(str1==str2); //false
這就是說,賦值的變化致使了類對象引用的變化,str1指向了另一個新對象!而str2仍舊指向原來的對象。上例中,當咱們將str1的值改成"bcd"時,JVM發如今棧中沒有存放該值的地址,便開闢了這個地址,並建立了一個新的對象,其字符串的值指向這個地址。
事實上,String類被設計成爲不可改變(immutable)的類。若是你要改變其值,能夠,但JVM在運行時根據新值悄悄建立了一個新對象,而後將這個對象的地址返回給原來類的引用。這個建立過程雖然說是徹底自動進行的,但它畢竟佔用了更多的時間。在對時間要求比較敏感的環境中,會帶有必定的不良影響。
再修改原來代碼:
String str1 = "abc";
String str2 = "abc";
str1 = "bcd";
String str3 = str1;
System.out.println(str3); //bcd
String str4 = "bcd";
System.out.println(str1 == str4); //true
str3這個對象的引用直接指向str1所指向的對象(注意,str3並無建立新對象)。當str1改完其值後,再建立一個String的引用str4,並指向因str1修改值而建立的新的對象。能夠發現,這回str4也沒有建立新的對象,從而再次實現棧中數據的共享。
咱們再接着看如下的代碼。
String str1 = new String("abc");
String str2 = "abc";
System.out.println(str1==str2); //false
建立了兩個引用。建立了兩個對象。兩個引用分別指向不一樣的兩個對象。
String str1 = "abc";
String str2 = new String("abc");
System.out.println(str1==str2); //false
建立了兩個引用。建立了兩個對象。兩個引用分別指向不一樣的兩個對象。
以上兩段代碼說明,只要是用new()來新建對象的,都會在堆中建立,並且其字符串是單獨存值的,即便與棧中的數據相同,也不會與棧中的數據共享。
6. 數據類型包裝類的值不可修改。不只僅是String類的值不可修改,全部的數據類型包裝類都不能更改其內部的值。
7. 結論與建議:
(1)咱們在使用諸如String str = "abc";的格式定義類時,老是想固然地認爲,咱們建立了String類的對象str。擔憂陷阱!對象可能並無被建立!惟一能夠確定的是,指向String類的引用被建立了。至於這個引用究竟是否指向了一個新的對象,必須根據上下文來考慮,除非你經過new()方法來顯要地建立一個新的對象。所以,更爲準確的說法是,咱們建立了一個指向String類的對象的引用變量str,這個對象引用變量指向了某個值爲"abc"的String類。清醒地認識到這一點對排除程序中難以發現的bug是頗有幫助的。
(2)使用String str = "abc";的方式,能夠在必定程度上提升程序的運行速度,由於JVM會自動根據棧中數據的實際狀況來決定是否有必要建立新對象。而對於String str = new String("abc");的代碼,則一律在堆中建立新對象,而無論其字符串值是否相等,是否有必要建立新對象,從而加劇了程序的負擔。這個思想應該是享元模式的思想,但JDK的內部在這裏實現是否應用了這個模式,不得而知。
(3)當比較包裝類裏面的數值是否相等時,用equals()方法;當測試兩個包裝類的引用是否指向同一個對象時,用「==」。
(4)因爲String類的immutable性質,當String變量須要常常變換其值時,應該考慮使用StringBuffer類,以提升程序效率。
2.5 方法區
JVM有一個被全部的線程共享方法區。方法區相似於傳統語言的編譯後代碼的存儲區,或者UNIX進程中的text段。它存儲每一個類結構例如常量池(constant pool),成員字段域和方法和構造函數,包含類和實例初始化和接口類型類型中用到的特殊方法的代碼。
方法區在虛擬機啓動時建立。儘管方法區在邏輯上時heap的一部分,簡單的實現仍然能夠選擇對它既不回收也不壓縮。
The Java virtual machine has a method area that is shared among all Java virtual machine threads. The method area is analogous to the storage area for compiled code of a conventional language or analogous to the "text" segment in a UNIX process. It stores per-class structures such as the runtime constant pool, field and method data, and the code for methods and constructors, including the special methods (§3.9) used in class and instance initialization and interface type initialization.
The method area is created on virtual machine start-up. Although the method area is logically part of the heap, simple implementations may choose not to either garbage collect or compact it. This version of the Java virtual machine specification does not mandate the location of the method area or the policies used to manage compiled code. The method area may be of a fixed size or may be expanded as required by the computation and may be contracted if a larger method area becomes unnecessary. The memory for the method area does not need to be contiguous.
A Java virtual machine implementation may provide the programmer or the user control over the initial size of the method area, as well as, in the case of a varying-size method area, control over the maximum and minimum method area size.
Java中變量分爲靜態變量,實例變量,臨時變量。那麼各類變量具體保存在JVM中的何處呢?
1 靜態變量:位於方法區。
2 實例變量:做爲對象的一部分,保存在堆中。
3 臨時變量:保存於棧中,棧隨線程的建立而被分配。
注:常量:位於常量池,而常量池位於方法區,若JVM採用的是分代垃圾回收,則方法區就是Perm區(永久存儲區)。