
如果是一個(gè)類(lèi)里面的靜態(tài)成員變量和靜態(tài)成員方法,它是存儲(chǔ)在方法區(qū)的,靜態(tài)成員變量是在方法區(qū)的靜態(tài)域里面,而靜態(tài)成員方法是在方法區(qū)的class二進(jìn)制信息里面(.class文件和方法區(qū)里面的二進(jìn)制信息不一樣,讀取.class文件按照虛擬機(jī)需要的格式存儲(chǔ)在方法區(qū)。這種格式包括數(shù)據(jù)結(jié)構(gòu)方面),靜態(tài)成員和靜態(tài)成員方法使用時(shí)不用創(chuàng)建對(duì)象,即類(lèi)加載初始化后就可以使用,并且是線程共享的。
通過(guò)圖中分析,很多問(wèn)題也能夠迎刃而解,比如不同線程調(diào)用方法為什么是線程安全的。局部變量存儲(chǔ)在哪兒里(棧中),成員變量存儲(chǔ)在哪兒里(靜態(tài)成員變量存儲(chǔ)在方法區(qū),非靜態(tài)成員變量存儲(chǔ)在堆區(qū)),為什么局部變量不能夠static修飾等(局部變量存儲(chǔ)在棧區(qū),在方法調(diào)用時(shí)不能夠自動(dòng)初始化必須由程序員手動(dòng)初始化,否則會(huì)報(bào)錯(cuò),歸根結(jié)底是由于static變量和局部變量存儲(chǔ)的位置不一樣。)。
Java內(nèi)存空間個(gè)人理解
堆:堆主要存放Java在運(yùn)行過(guò)程中new出來(lái)的對(duì)象,凡是通過(guò)new生成的對(duì)象都存放在堆中,對(duì)于堆中的對(duì)象生命周期的管理由Java虛擬機(jī)的垃圾回收機(jī)制GC進(jìn)行回收和統(tǒng)一管理。類(lèi)的非靜態(tài)成員變量也放在堆區(qū),其中基本數(shù)據(jù)類(lèi)型是直接保存值,而復(fù)雜類(lèi)型是保存指向?qū)ο蟮囊茫庆o態(tài)成員變量在類(lèi)的實(shí)例化時(shí)開(kāi)辟空間并且初始化。所以你要知道類(lèi)的幾個(gè)時(shí)機(jī),加載-連接-初始化-實(shí)例化。
棧:棧主要存放在運(yùn)行期間用到的一些局部變量(基本數(shù)據(jù)類(lèi)型的變量)或者是指向其他對(duì)象的一些引用,因?yàn)榉椒▓?zhí)行時(shí),被分配的內(nèi)存就在棧中,所以當(dāng)然存儲(chǔ)的局部變量就在棧中咯。當(dāng)一段代碼或者一個(gè)方法調(diào)用完畢后,棧中為這段代碼所提供的基本數(shù)據(jù)類(lèi)型或者對(duì)象的引用立即被釋放;
常量池:常量池是方法區(qū)的一部分內(nèi)存。常量池在編譯期間就將一部分?jǐn)?shù)據(jù)存放于該區(qū)域,包含基本數(shù)據(jù)類(lèi)型如int、long等以final聲明的常量值,和String字符串、特別注意的是對(duì)于方法運(yùn)行期位于棧中的局部變量String常量的值可以通過(guò) String.intern()方法將該值置入到常量池中。
靜態(tài)域:位于方法區(qū)的一塊內(nèi)存。存放類(lèi)中以static聲明的靜態(tài)成員變量
方法區(qū):是各個(gè)線程共享的內(nèi)存區(qū)域,它用于存儲(chǔ)class二進(jìn)制文件,包含了虛擬機(jī)加載的類(lèi)信息、常量、靜態(tài)變量、即時(shí)編譯后的代碼等數(shù)據(jù)。它有個(gè)名字叫做Non-Heap(非堆),目的是與Java堆區(qū)分開(kāi)。
需要特別注意的是:
方法區(qū)是線程安全的。由于所有的線程都共享方法區(qū),所以,方法區(qū)里的數(shù)據(jù)訪問(wèn)必須被設(shè)計(jì)成線程安全的。例如,假如同時(shí)有兩個(gè)線程都企圖訪問(wèn)方法區(qū)中的同一個(gè)類(lèi),而這個(gè)類(lèi)還沒(méi)有被裝入JVM,那么只允許一個(gè)線程去裝載它,而其它線程必須等待 !
最后總結(jié)起來(lái)就是:
棧:為即時(shí)調(diào)用的方法開(kāi)辟空間,存儲(chǔ)局部變量值(基本數(shù)據(jù)類(lèi)型),局部變量引用。注意:局部變量必須手動(dòng)初始化。
堆:存放引用類(lèi)型的對(duì)象,即new出來(lái)的對(duì)象、數(shù)組值、類(lèi)的非靜態(tài)成員變量值(基本數(shù)據(jù)類(lèi)型)、非靜態(tài)成員變量引用。其中非靜態(tài)成員變量在實(shí)例化時(shí)開(kāi)辟空間初始化值。更具體點(diǎn),個(gè)人感覺(jué)非靜態(tài)成員變量是放在堆的對(duì)象中。
方法區(qū):存放class二進(jìn)制文件。包含類(lèi)信息、靜態(tài)變量,常量池(String字符串和final修飾的常量值等),類(lèi)的版本號(hào)等基本信息。因?yàn)槭枪蚕淼膮^(qū)域,所以如果靜態(tài)成員變量的值或者常量值(String類(lèi)型的值能夠非修改,具體請(qǐng)查看博客)被修改了直接就會(huì)反應(yīng)到其它類(lèi)的對(duì)象中。
成員變量與局部變量總結(jié):
一:在方法中聲明的變,即該變量是局部變量,每當(dāng)程序調(diào)用方法時(shí),系統(tǒng)都會(huì)為該方法建立一個(gè)方法棧,其所在方法中聲明的變量就放在方法棧中,當(dāng)方法結(jié)束系統(tǒng)會(huì)釋放方法棧,其對(duì)應(yīng)在該方法中聲明的變量隨著棧的銷(xiāo)毀而結(jié)束,這就局部變量只能在方法中有效的原因<1>在方法中生明的變量可以是基本類(lèi)型的變量,也可以是引用類(lèi)型的變量,(1)當(dāng)聲明是基本類(lèi)型的變量的時(shí),其變量名及值(變量名及值是兩個(gè)概念)是放在方法棧中(2)當(dāng)聲明的是引用變量時(shí),所聲明的變量(該變量實(shí)際上是在方法中存儲(chǔ)的是內(nèi)存地址值)是放在方法的棧中,該變量所指向的對(duì)象是放在堆類(lèi)存中的》》》二:在類(lèi)中聲明的變量是成員變量,也叫全局變量,放在堆中的,<1>同樣在類(lèi)中聲明的變量即可是基本類(lèi)型的變量 也可是引用類(lèi)型的變量(1)當(dāng)聲明的是基本類(lèi)型的變量其變量名及其只時(shí)放在堆類(lèi)存中的,(2)引用類(lèi)型時(shí),其聲明的變量仍然會(huì)存儲(chǔ)一個(gè)內(nèi)存地址值,該內(nèi)存地址值指向所引用的對(duì)象
下面給大家看一個(gè)Java代碼例子:
聲明一個(gè)類(lèi):
public class A {
? ? public final String tempString="world";//這里可以把final去掉,結(jié)果等同!!
? ? public final char[] charArray="Hello".toCharArray();
? ? public char[] getCharArray() {
? ? ? ? return charArray;
? ? }
? ? public String getTempString() {
? ? ? ? return tempString;
? ? }
}
創(chuàng)建測(cè)試類(lèi):
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);
? ? }
}
輸出結(jié)果:
false
true
要想明白上面字符串對(duì)比為什么輸出為true你必須知道:

該圖片截自《深入理解Java虛擬機(jī)》
一個(gè)Class字節(jié)碼文件的Class字節(jié)碼文件對(duì)象只有一個(gè)常量池,常量池被所有線程共享。
在常量池中,字符串被存儲(chǔ)為一個(gè)字符序列,每個(gè)字符序列都對(duì)應(yīng)一個(gè)String對(duì)象,該對(duì)象保存在堆中。所以也就是說(shuō)為什么String
temp=“xxx”;能夠當(dāng)成一個(gè)對(duì)象使用??!
如果多個(gè)線程去訪問(wèn)A類(lèi)中的String字符串,每次都會(huì)到常量區(qū)中去找該字符序列的引用。
所以訪問(wèn)A類(lèi)被創(chuàng)建的兩個(gè)A類(lèi)型對(duì)象的String字符串對(duì)比會(huì)輸出true。
如果對(duì)字符串輸出為true還是不懂,可以參考這篇博客:字符串被存儲(chǔ)的原理過(guò)程
那么為什么final類(lèi)型的字符數(shù)組就不為true了呢??
申明(不管是通過(guò)new還是通過(guò)直接寫(xiě)一個(gè)數(shù)組)一個(gè)數(shù)組其實(shí)在Java中就等同創(chuàng)建了一個(gè)對(duì)象,即每次創(chuàng)建類(lèi)的對(duì)象都會(huì)自動(dòng)創(chuàng)建一個(gè)新的數(shù)組空間。
其中要注意的是:常量池中存儲(chǔ)字符數(shù)組只是存儲(chǔ)的是每個(gè)字符或者字符串。
為了證明每次獲取的final數(shù)組地址不一樣,并且數(shù)組中的字符都會(huì)存儲(chǔ)在常量池中,我們需要參考另外一個(gè)代碼:
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;
? ? }
}
測(cè)試代碼:
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類(lèi)型的數(shù)組地址也都不一樣??!但是String類(lèi)型數(shù)組中的每個(gè)字符串都存儲(chǔ)在常量池中。
所以可以肯定的是字符串和其它能夠確定值的final字面量值是存儲(chǔ)在常量池的!!并且在方法區(qū)內(nèi)存中只有一份??!與所有線程共享訪問(wèn)?。?/p>
常量池存儲(chǔ)的項(xiàng)目類(lèi)型: