先放一張圖:
1.棧內存中放哪些東西?
- 基本類型的變量,例如int a=3中的a;
- 對象的引用變量,例如Thread t=new Thread();中的t。
當在代碼塊中定義一個變量時,Java就在棧中爲這個變量分配內存空間;當超過變量的作用域後,Java會自動 釋放掉爲該變量分配的內存空間,該內存空間可以立刻被另作他用。
2.堆內存中存放哪些東西?
存放由new創建的對象和數組。
在堆中存放的內存,由Java虛擬機垃圾回收器來管理。在堆中產生了一個數組或者對象後,還可以在棧中定義一個特殊的變量,這個變量持有的內容等於數組或者對象在堆內存中的首地址。在棧中的這個特殊的變量,就成了數組或者對象的引用變量,以後就可以在程序中使用棧內存中的引用變量來訪問堆中的數組或者對象,引用變量相當於爲數組或者對象起的一個別名,或者代號。
3.靜態區/方法區:
方法區(method)也叫做靜態區,跟堆一樣,被所有的線程共享。方法區包含所有的class和static變量。
方法區中包含的都是在整個程序中永遠唯一的元素,例如class,static變量。
全局變量和靜態變量的存儲是放在一塊的,初始化的全局變量和靜態變量在一塊區域,未初始化的全局變量和未初始化的靜態變量放在相鄰的另一塊區域。
堆內存和棧內存,兩者的區別?
(1)引用變量是普通變量,定義時在棧內存中分配,引用變量在程序運行到作用域外後被釋放。而數組和對象本身在堆中分配,即使程序運行到使用new產生數組和對象的語句所在的代碼塊之外,數組和對象本身佔用的堆內存也不會被釋放。數組和對象在沒有引用變量指向它的時候,才變成垃圾,不能再被使用,但是仍然佔着內存,在隨後的一個不確定的時間被垃圾回收器釋放掉,這個也是Java比較佔內存的主要原因。實際上,棧中的引用變量指向堆內存中的變量,這就是Java中的指針。
(2)通俗來講,堆是用來存放對象的,而棧是用來執行程序的。
(3)jvm只有一個堆區(heap),被所有線程共享;
每個線程包含一個棧區(stack),每個棧中的數據都是私有的,其他的棧不能訪問,但是同一個棧中變量是共享的;分爲3個部分:基本類型變量區,執行環境上下文,操作指令區。
爲什麼要有堆和棧?這樣設計有什麼好處?
(1)Java自動管理堆和棧,程序員不能直接地設置棧和堆。
(2)Java的堆是一個運行時數據區。堆是由JVM的垃圾回收器自動管理的。堆的優勢是可以在程序運行時,動態地分配內存
大小,但是正是由於這個原因,它的存取速度較慢。
(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=4;那麼編譯器會重新搜索棧中是否有4值,如果沒有,則將4存放進來,並令a指向4;如果已經有了,則直接將a指向這個地址。因此a值的改變不會影響到b的值。
要注意這種數據的共享與兩個對象引用,指向一個對象的這種共享是不同的,因爲這種情況a的修改並不會影響到b, 它是由編譯器完成的,它有利於節省空間。而一個對象引用變量修改了這個對象的內部狀態,會影響到另一個對象引用變量。
下面看這段代碼在內存中的分配流程:
package test01;
public class StackHeadMethod {
public static void main(String[] args) {
int a = 1; // 在棧中分配內存空間,A
String b = "testBName"; // 在棧和方法區中分配內存空間, B
Test test = new Test(); // 在棧中堆中分配內存空間, C
test.testMethod(a, b); // , D
}
}
class Test {
private int testA; // 在堆中分配內存空間, E
private String testB; // 在堆中分配內存空間, F
private static final int testC = 2; // 在堆和方法區中分配內存空間, G
public void testMethod(int testA, String testB){
this.testA = testA;
this.testB = testB;
}
}
首先,JVM將StackHeadMethod.class、Test.class裝載到方法區(JVM會執行啓動類裝載器、擴展類裝載器和類路徑裝載器,在此只詳細講解針對本測試代碼的字節碼文件的執行)。其中方法區中的2是Test.java中的靜態變量,在類加載的時候就在方法區中的靜態存儲空間分配內存。
然後,在StackHeadMethod.class文件中,找到main方法,開始執行main方法。將main方法在棧中開闢一個空間,稱爲棧幀。執行下面A、B、C、E、F、G行代碼。
再然後,執行test.testMethod(a, b);這一行,在棧中新分配一個棧幀,調用test中的testMethod方法。
最後,testMethod方法執行完之後,testMethod棧幀從棧中釋放空間,然後main方法執行完之後,main棧幀也釋放空間,最後堆中的對象和方法區中的靜態變量、字符串和字節碼指令都沒被使用時,根據java虛擬機的垃圾回收機制,進行對垃圾的回收。
以上,是執行一段代碼,對內存進行開闢和釋放的整個過程。
參考:棧,堆,方法區關係圖