說到Java中堆、棧和常量池,首先還是看看他們各自存放的數據類型吧!
棧:
Java的JVM的內存可分為3個區:堆(heap)、棧(stack)和方法區(method)也叫靜態存儲區。
堆區:(存放所有new出來的對象;)
1.存儲的全部是對象,每個對象都包含一個與之對應的class的信息。(class的目的是得到操作指令)
2.jvm只有一個堆區(heap)被所有線程共享,堆中不存放基本類型和對象引用,只存放對象本身
棧區:(存放基本類型的變量數據和對象的應用,對象(new出來的對象)本身並不存在棧中,而是存放在堆中或者常量池中(字符串常量對象存放在常量池中))
1.每個線程包含一個棧區,棧中只保存基礎數據類型的對象(比如int i=1中1就是基礎類型的對象)和自定義對象的引用(不是對象)而真實對象都存放在堆區中
2.每個棧中的數據(原始類型和對象引用)都是私有的,其他棧不能訪問。
3.棧分為3個部分:基本類型變量區、執行環境上下文、操作指令區(存放操作指令)。
常量池:存放基本類型常量和字符串常量。
方法區:
1.又叫靜態區,跟堆一樣,被所有的線程共享。方法區包含所有的class和static變量。
2.方法區中包含的都是在整個程序中永遠唯一的元素,如class,static變量名,不是常量(它在堆棧中)。
(指數據共享時)對於棧和常量池中的數據可以共享(具有多個引用對象),對於堆中的對象不可以共享(一個引用對象)。 (指線程共享時)比如:同一個進程的多個線程堆棧共享狀況哪個描述正確?線程共享堆,但是每個線程有自己的寄存器和棧
解析: 線程占有的都是不共享的,其中包括:棧、寄存器、狀態、程序計數器
線程間共享的有:堆,全局變量,靜態變量,主要指共享進程的資源;
進程占有的資源有:地址空間,全局變量,打開的文件,子進程,信號量、賬戶信息。
棧中的數據大小和生命周期是可以確定的,當沒有引用指向數據時,這個數據就會自動消失。堆中的對象的由垃圾回收器負責回收,因此大小和生命周期不需要確定,具有很大的靈活性。
而對於字符串來說,其對象的引用都是存儲在棧中的,如果是編譯期已經創建好(即指用雙引號定義的)的就存儲在常量池中,如果是運行期(new出來的對象)則存儲在堆中。對於equals相等的字符串,在常量池中是只有一份的,在堆中則有多份。
因為方法區是被所有線程共享的,所以必須考慮數據的線程安全。假如兩個線程都在試圖找lava的類,在lava類還沒有被加載的情況下,只應該有一個線程去加載,而另一個線程等待。
方法區的大小不必是固定的,jvm可以根據應用的需要動態調整。同樣方法區也不必是連續的。方法區可以在堆(甚至是虛擬機自己的堆)中分配。jvm可以允許用戶和程序指定方法區的初始大小,最小和最大尺寸。
方法區同樣存在垃圾收集,因為通過用戶定義的類加載器可以動態擴展Java程序,一些類也會成為垃圾。jvm可以回收一個未被引用的(沒有實例對象)類所占的空間,以使方法區的空間最小。
要處理好動態派發,一種可能的實現方式是專門開辟一個區,單獨管理所有方法,可以按照穩定性對於該對象的所有方法(當然包括父類方法)進行穩定性排序,使相同方法聚集在一起。當方法調用時在方法區搜索的時候一次定位到相同方法的位置的起始處,不管第一個命中方法是父類還是子類,也不管此處相同方法的個數,始終執行第一個。
作者:尋寒
鏈接:https://www.zhihu.com/question/23599282/answer/25076568
來源:知乎
著作權歸作者所有,轉載請聯系作者獲得授權。
舉個例子吧!
Java代碼
String s1 = "china";
String s2 = "china";
String s3 = "china";
String ss1 = new String("china");
String ss2 = new String("china");
String ss3 = new String("china");
如以下代碼:
Java代碼
int i1 = 9;
int i2 = 9;
int i3 = 9;
public static final int INT1 = 9;
public static final int INT2 = 9;
public static final int INT3 = 9;

對於成員變量和局部變量:成員變量就是方法外部,類的內部定義的變量;局部變量就是方法或語句塊內部定義的變量。局部變量必須初始化。
形式參數是局部變量,局部變量的數據存在於棧內存中。棧內存中的局部變量隨着方法的消失而消失。
成員變量存儲在堆中的對象里面,由垃圾回收器負責回收。
如以下代碼:
Java代碼
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()將等待垃圾回收。