如果是一個類里面的靜態(tài)成員變量和靜態(tài)成員方法猜拾,它是存儲在方法區(qū)的即舌,靜態(tài)成員變量是在方法區(qū)的靜態(tài)域里面,而靜態(tài)成員方法是在方法區(qū)的class二進制信息里面(.class文件和方法區(qū)里面的二進制信息不一樣挎袜,讀取.class文件按照虛擬機需要的格式存儲在方法區(qū)顽聂。這種格式包括數(shù)據(jù)結構方面)肥惭,靜態(tài)成員和靜態(tài)成員方法使用時不用創(chuàng)建對象,即類加載初始化后就可以使用芜飘,并且是線程共享的务豺。
通過圖中分析,很多問題也能夠迎刃而解嗦明,比如不同線程調(diào)用方法為什么是線程安全的笼沥。局部變量存儲在哪兒里(棧中),成員變量存儲在哪兒里(靜態(tài)成員變量存儲在方法區(qū)娶牌,非靜態(tài)成員變量存儲在堆區(qū))奔浅,為什么局部變量不能夠static修飾等(局部變量存儲在棧區(qū),在方法調(diào)用時不能夠自動初始化必須由程序員手動初始化诗良,否則會報錯托猩,歸根結底是由于static變量和局部變量存儲的位置不一樣单寂。)。
Java內(nèi)存空間個人理解
堆:堆主要存放Java在運行過程中new出來的對象,凡是通過new生成的對象都存放在堆中千劈,對于堆中的對象生命周期的管理由Java虛擬機的垃圾回收機制GC進行回收和統(tǒng)一管理皆刺。類的非靜態(tài)成員變量也放在堆區(qū)休讳,其中基本數(shù)據(jù)類型是直接保存值颠黎,而復雜類型是保存指向?qū)ο蟮囊茫庆o態(tài)成員變量在類的實例化時開辟空間并且初始化总处。所以你要知道類的幾個時機狈惫,加載-連接-初始化-實例化。
棧:棧主要存放在運行期間用到的一些局部變量(基本數(shù)據(jù)類型的變量)或者是指向其他對象的一些引用鹦马,因為方法執(zhí)行時胧谈,被分配的內(nèi)存就在棧中,所以當然存儲的局部變量就在棧中咯荸频。當一段代碼或者一個方法調(diào)用完畢后菱肖,棧中為這段代碼所提供的基本數(shù)據(jù)類型或者對象的引用立即被釋放;
常量池:常量池是方法區(qū)的一部分內(nèi)存旭从。常量池在編譯期間就將一部分數(shù)據(jù)存放于該區(qū)域稳强,包含基本數(shù)據(jù)類型如int、long等以final聲明的常量值遇绞,和String字符串键袱、特別注意的是對于方法運行期位于棧中的局部變量String常量的值可以通過 String.intern()方法將該值置入到常量池中燎窘。
靜態(tài)域:位于方法區(qū)的一塊內(nèi)存摹闽。存放類中以static聲明的靜態(tài)成員變量
方法區(qū):是各個線程共享的內(nèi)存區(qū)域,它用于存儲class二進制文件褐健,包含了虛擬機加載的類信息付鹿、常量澜汤、靜態(tài)變量、即時編譯后的代碼等數(shù)據(jù)舵匾。它有個名字叫做Non-Heap(非堆)俊抵,目的是與Java堆區(qū)分開。
需要特別注意的是:
方法區(qū)是線程安全的坐梯。由于所有的線程都共享方法區(qū)徽诲,所以,方法區(qū)里的數(shù)據(jù)訪問必須被設計成線程安全的吵血。例如谎替,假如同時有兩個線程都企圖訪問方法區(qū)中的同一個類,而這個類還沒有被裝入JVM蹋辅,那么只允許一個線程去裝載它钱贯,而其它線程必須等待 !
最后總結起來就是:
棧:為即時調(diào)用的方法開辟空間侦另,存儲局部變量值(基本數(shù)據(jù)類型)秩命,局部變量引用。注意:局部變量必須手動初始化褒傅。
堆:存放引用類型的對象弃锐,即new出來的對象、數(shù)組值樊卓、類的非靜態(tài)成員變量值(基本數(shù)據(jù)類型)拿愧、非靜態(tài)成員變量引用。其中非靜態(tài)成員變量在實例化時開辟空間初始化值碌尔。更具體點浇辜,個人感覺非靜態(tài)成員變量是放在堆的對象中。
方法區(qū):存放class二進制文件唾戚。包含類信息柳洋、靜態(tài)變量,常量池(String字符串和final修飾的常量值等)叹坦,類的版本號等基本信息熊镣。因為是共享的區(qū)域,所以如果靜態(tài)成員變量的值或者常量值(String類型的值能夠非修改募书,具體請查看博客)被修改了直接就會反應到其它類的對象中绪囱。
成員變量與局部變量總結:
一:在方法中聲明的變,即該變量是局部變量莹捡,每當程序調(diào)用方法時鬼吵,系統(tǒng)都會為該方法建立一個方法棧,其所在方法中聲明的變量就放在方法棧中篮赢,當方法結束系統(tǒng)會釋放方法棧齿椅,其對應在該方法中聲明的變量隨著棧的銷毀而結束琉挖,這就局部變量只能在方法中有效的原因<1>在方法中生明的變量可以是基本類型的變量,也可以是引用類型的變量涣脚,(1)當聲明是基本類型的變量的時示辈,其變量名及值(變量名及值是兩個概念)是放在方法棧中(2)當聲明的是引用變量時,所聲明的變量(該變量實際上是在方法中存儲的是內(nèi)存地址值)是放在方法的棧中遣蚀,該變量所指向的對象是放在堆類存中的》》》二:在類中聲明的變量是成員變量矾麻,也叫全局變量,放在堆中的芭梯,<1>同樣在類中聲明的變量即可是基本類型的變量 也可是引用類型的變量(1)當聲明的是基本類型的變量其變量名及其只時放在堆類存中的射富,(2)引用類型時,其聲明的變量仍然會存儲一個內(nèi)存地址值粥帚,該內(nèi)存地址值指向所引用的對象
下面給大家看一個Java代碼例子:
聲明一個類:
public class A {
? ? public final String tempString="world";//這里可以把final去掉胰耗,結果等同!芒涡!
? ? public final char[] charArray="Hello".toCharArray();
? ? public char[] getCharArray() {
? ? ? ? return charArray;
? ? }
? ? public String getTempString() {
? ? ? ? return tempString;
? ? }
}
創(chuàng)建測試類:
public class TestA {
? ? public static void main(String[] args) {
? ? ? ? A a1=new? A();
? ? ? ? A a2=new A();
? ? ? ? System.out.println(a1.charArray==a2.charArray);
? ? ? ? System.out.println(a1.tempString==a2.tempString);
? ? }
}
輸出結果:
false
true
要想明白上面字符串對比為什么輸出為true你必須知道:
該圖片截自《深入理解Java虛擬機》
一個Class字節(jié)碼文件的Class字節(jié)碼文件對象只有一個常量池柴灯,常量池被所有線程共享。
在常量池中费尽,字符串被存儲為一個字符序列赠群,每個字符序列都對應一個String對象,該對象保存在堆中旱幼。所以也就是說為什么String
temp=“xxx”;能夠當成一個對象使用2槊琛!
如果多個線程去訪問A類中的String字符串柏卤,每次都會到常量區(qū)中去找該字符序列的引用冬三。
所以訪問A類被創(chuàng)建的兩個A類型對象的String字符串對比會輸出true。
如果對字符串輸出為true還是不懂缘缚,可以參考這篇博客:字符串被存儲的原理過程
那么為什么final類型的字符數(shù)組就不為true了呢勾笆??
申明(不管是通過new還是通過直接寫一個數(shù)組)一個數(shù)組其實在Java中就等同創(chuàng)建了一個對象桥滨,即每次創(chuàng)建類的對象都會自動創(chuàng)建一個新的數(shù)組空間窝爪。
其中要注意的是:常量池中存儲字符數(shù)組只是存儲的是每個字符或者字符串。
為了證明每次獲取的final數(shù)組地址不一樣齐媒,并且數(shù)組中的字符都會存儲在常量池中蒲每,我們需要參考另外一個代碼:
public class A {
? ? public String tempString="world";
? ? public final String tempStringArray[]={"Fire","Lang"};
? ? public final char[] charArray={'h','e','l','l','o'};
? ? public Character charx='l';
? ? public char[] getCharArray() {
? ? ? ? return charArray;
? ? }
? ? public String getTempString() {
? ? ? ? return tempString;
? ? }
? ? public String[] getTempStringArray() {
? ? ? ? return tempStringArray;
? ? }
? ? public Character getCharx() {
? ? ? ? return charx;
? ? }
}
測試代碼:
public class TestA {
? ? public static void main(String[] args) {
? ? ? ? A a1=new? A();
? ? ? ? A a2=new A();
? ? ? ? System.out.println(a1.tempString==a2.tempString);
? ? ? ? System.out.println(a1.tempStringArray==a2.tempStringArray);//看這里
? ? ? ? System.out.println("#####################");//看這里
? ? ? ? System.out.println(a1.tempStringArray[0]==a2.tempStringArray[0]);
? ? ? ? System.out.println(a1.tempStringArray[0]=="Fire");
? ? ? ? System.out.println("#####################");
? ? ? ? System.out.println(a1.charArray==a2.charArray);
? ? ? ? System.out.println(a1.charx==a2.charx);
? ? }
}
輸出:
true
false
#####################
true
true
#####################
false
true
可以看到每次輸出的final數(shù)組地址都不一樣,最重要的是String類型的數(shù)組地址也都不一樣S骼ā邀杏!但是String類型數(shù)組中的每個字符串都存儲在常量池中。
所以可以肯定的是字符串和其它能夠確定值的final字面量值是存儲在常量池的K痢淮阐!并且在方法區(qū)內(nèi)存中只有一份!刁品!與所有線程共享訪問F亍!
常量池存儲的項目類型: