1逗宜、Java棧
Java在函數(shù)中定義的基本類型(int,long,short,byte,float,double,boolean,char)的變量(局部變量和函數(shù)的形參)的引用和數(shù)據(jù),以及對象的引用都放在棧中存儲(chǔ)空骚。
1纺讲、棧的特點(diǎn)
- 1、存取速度快囤屹。僅次于CPU中的寄存器熬甚。
- 2、每個(gè)線程都會(huì)有一個(gè)椑呒幔空間乡括,不同棧之間不能直接訪問,所以線程之間不能共享?xiàng)V械臄?shù)據(jù)智厌。
- 3诲泌、存在棧中的數(shù)據(jù)是可以共享的。
比如我們定義
int a=3铣鹏;int b=3敷扫;a=4;
編譯器先處理int a=3诚卸;
首先會(huì)在棧中創(chuàng)建一個(gè)變量a的引用葵第,然后在棧中查找有沒有字面值為3的地址绘迁,如果有則將a指向這個(gè)地址;如果沒有則開辟一塊內(nèi)存放字面值3卒密,然后將a引用指向這個(gè)地址缀台。
接著處理int b=3;
哮奇,在棧中創(chuàng)建b的引用變量膛腐,由于在棧中字面值為3的地址已經(jīng)存在,所以直接將b指向這個(gè)地址屏镊。這樣a和b同時(shí)指向了字面值為3的這個(gè)地址依疼。
接著處理a=4;
而芥,會(huì)在棧中查找字面值為4的地址,如沒有就開辟內(nèi)存存放字面值4膀值,讓a指向這個(gè)地址棍丐,如有就將a直接指向這個(gè)地址。
此時(shí)b依然等于3沧踏,不會(huì)等于4歌逢。這點(diǎn)和對象的引用不同,需要注意翘狱。
- 4秘案、如果棧內(nèi)存中沒有足夠的空間可以使用,JVM會(huì)拋出java.lang.StackOverFlowError異常潦匈。
- 5阱高、棧中定義的變量,在超出變量的作用域后茬缩,Java會(huì)自動(dòng)釋放為變量所分配的內(nèi)存空間赤惊,該內(nèi)存空間可以立即被另作他用。
2凰锡、堆
堆中主要用于存放new出來的對象和數(shù)組未舟。下面舉例看下對象的實(shí)例化過程。
Person a=new Person ("123")掂为;
Person b=new Person ("123")裕膀;
編譯器會(huì)先執(zhí)行Person a=new Person ("123");
勇哗,會(huì)在堆中開辟內(nèi)存存放創(chuàng)建的對象Person ("123")
昼扛,在棧中創(chuàng)建變量a,將a指向?qū)ο蟮膬?nèi)存首地址智绸,a就是該對象的引用野揪。
接著執(zhí)行Person b=new Person ("123")访忿;
,雖然前面已經(jīng)創(chuàng)建了對象Person ("123")
斯稳,但是只要使用關(guān)鍵字new
海铆,就會(huì)在堆中開辟一塊新的內(nèi)存存放創(chuàng)建的對象,在棧中創(chuàng)建變量b挣惰,將b指向新對象的首地址卧斟。所以a和b指向并不是同一個(gè)對象。
如果定義Person c = a憎茂;a.setName("234")
那么String name=c.getName()
就等于“234”。
執(zhí)行Person c = a竖幔;
則變量a和c都會(huì)指向同一個(gè)對象板乙,所以使用a變量修改對象中的內(nèi)容時(shí),c指向的對象的內(nèi)容也會(huì)改變拳氢。
2.1募逞、特點(diǎn)
- 1、存取速度比棧中的慢馋评。
- 2放接、一個(gè)JVM只有一個(gè)堆內(nèi)存,所以線程間是可以共享堆內(nèi)存中的數(shù)據(jù)的留特。
- 3纠脾、如果堆中內(nèi)存不足,則會(huì)拋出java.lang.OutOfMemoryError異常蜕青。
- 4苟蹈、定義在棧中的變量a指向堆中的對象Person的首地址,在超出變量的作用域后市咆,Java會(huì)自動(dòng)釋放a所分配的內(nèi)存空間汉操,此時(shí)就沒有引用指向?qū)ο罅耍菍ο蟛⒉粫?huì)馬上被回收蒙兰,需要等某個(gè)時(shí)間通過垃圾回收來回收內(nèi)存磷瘤。
3、常量池
常量池分為兩種:靜態(tài)常量池和運(yùn)行時(shí)常量池搜变。
靜態(tài)常量池
靜態(tài)常量池指的是在編譯期確定采缚,保存在class文件中的一些數(shù)據(jù)。常量池主要用于存放兩大類常量:字面量(Literal)和符號(hào)引用量挠他,字面量相當(dāng)于Java語言層面常量的概念扳抽,如文本字符串、聲明為final的常量值等,符號(hào)引用則屬于編譯原理方面的概念贸呢,包括了如下三種類型的常量:
- 1镰烧、類和接口的全限定名;
- 2楞陷、字段的名稱和描述符怔鳖;
- 3、方法的名稱和描述符固蛾。
運(yùn)行時(shí)常量池
在運(yùn)行時(shí)常量池是方法區(qū)的一部分结执,在JDK1.7之后運(yùn)行時(shí)常量池從方法區(qū)中移出,放在堆中艾凯。
常量池是為了避免頻繁的創(chuàng)建和銷毀對象而影響系統(tǒng)性能献幔,其實(shí)現(xiàn)了對象的共享。
3.1趾诗、字符串常量池
對于字符串蜡感,其對象的引用都是存儲(chǔ)在棧中的,如果是編譯期已經(jīng)創(chuàng)建好(直接用雙引號(hào)定義的)的就存儲(chǔ)在常量池中恃泪,如果是運(yùn)行期(new出來的)才能確定的就存儲(chǔ)在堆中铸敏。對于equals相等的字符串,在常量池中永遠(yuǎn)只有一份悟泵,在堆中有多份。
String s1 = "china";
String s2 = "china";
String s3 = "china";
String ss1 = new String("china");
String ss2 = new String("china");
String ss3 = new String("china");
s1==s2==s3闪水; //true
ss1!=ss2!=ss3!=s1!=s2!=ss3糕非; //true
ss1.eqauls(ss2);//true
ss1.eqauls(ss3)球榆;//true
ss1.eqauls(s1)朽肥;//true
ss1.eqauls(s2);//true
ss1.eqauls(s3)持钉;//true
可以看出s1衡招、s2、s3在編譯期就被創(chuàng)建每强,并存入到了常量池中始腾。編譯器在執(zhí)行
String s1 = "china";
時(shí)會(huì)先在常量池中查找是否存在字符串常量“china”,如果不存在就在常量池中new一個(gè)china字符串空执,存在就不new浪箭,然后讓棧中的變量指向這個(gè)china字符串。因此常量池中只有一個(gè)china字符串對象辨绊,然后在執(zhí)行String s2 = "china";String s3 = "china";
時(shí)奶栖,會(huì)在常量池中找到china字符串,并讓s2、s3指向它宣鄙。對于
String ss1 = new String("china");
在編譯時(shí)并不會(huì)創(chuàng)建袍镀,在運(yùn)行時(shí),通過new產(chǎn)生一個(gè)字符串(假設(shè)為“china”)時(shí)冻晤,會(huì)先去常量池中查找是否已經(jīng)存在字符串“china”苇羡,如果不存在則在常量池中創(chuàng)建一個(gè)“china”字符串對象,然后在堆中再創(chuàng)建一個(gè)常量池中的“china”對象的拷貝對象明也;如果常量池中存在宣虾,就直接在堆中創(chuàng)建一個(gè)常量池中此“china”對象的拷貝對象。
3.2温数、包裝類實(shí)現(xiàn)了常量池技術(shù)
對于8種基本數(shù)據(jù)類型大部分都有自己的包裝類绣硝,其中Byte,Short,Integer,Long,Character,Boolean都實(shí)現(xiàn)了常量池技術(shù)
,撑刺;而Byte,Short,Integer,Long類型在裝箱時(shí)會(huì)緩存了范圍[-128,127]的數(shù)據(jù)到數(shù)組中鹉胖,Character會(huì)緩存[0,127]范圍的數(shù)據(jù)到數(shù)組中進(jìn)行緩存。
- 1够傍、對于Integer來說甫菠,范圍是[-128,127]的數(shù)在自動(dòng)裝箱時(shí)全部被自動(dòng)加入到了常量池里面,具體可查看Integer.valueof(int i)方法冕屯。
Integer.valueOf()
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
IntegerCache類
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
實(shí)例
public class test {
public static void main(String[] args) {
Integer i1=10;
Integer i2=10;
System.out.println(i1==i2);
System.out.println(i1.equals(i2));
}
}
輸出:
true
true
- 2寂诱、使用new關(guān)鍵字創(chuàng)建Integer時(shí),即使數(shù)據(jù)在范圍[-128,127]安聘,也不會(huì)去緩存中查找痰洒,直接在堆中創(chuàng)建一個(gè)新的Integer對象。并不會(huì)像new String("123")可能需要在堆中或常量池中各創(chuàng)建一個(gè)對象浴韭。
Integer i1=new Integer(10);
Integer i2=new Integer(10);
Integer i3=10;
System.out.println(i1==i2); //false
System.out.println(i1.equals(i2)); //true
System.out.println(i1==i3); //false
System.out.println(i1.equals(i3)); //true
-3丘喻、當(dāng)整數(shù)不在[-128,127]范圍內(nèi)時(shí),就會(huì)在堆中創(chuàng)建對象念颈。
看下面例子在內(nèi)存中的分配泉粉。
public void test() {
int a1 = 9; //自動(dòng)拆箱 Integer.intValue()
int b1 = 9; //自動(dòng)拆箱 Integer.intValue()
final int A2 = 9;
final int B2 = 9;
Integer a3 = new Integer(9);
Integer b3 = new Integer(9);
Integer a4 = 9; //自動(dòng)裝箱 調(diào)用Integer.valueOf(int)
Integer b4 = 9; //自動(dòng)裝箱 調(diào)用Integer.valueOf(int)
}
如下圖
4、方法區(qū)
方法區(qū)也是各個(gè)線程共享的內(nèi)存區(qū)域榴芳,它用于存儲(chǔ)已經(jīng)被虛擬機(jī)加載的類信息嗡靡、常量、靜態(tài)變量翠语、即編譯器編譯后的代碼等數(shù)據(jù)叽躯。靜態(tài)變量、常量在方法區(qū),所有方法,包括靜態(tài)和非靜態(tài)的,也在方法區(qū)肌括。
5点骑、成員變量和局部變量在內(nèi)存中的分配
對于成員變量和局部變量:成員變量就是方法外部酣难,類的內(nèi)部定義的變量;局部變量就是方法或語句塊內(nèi)部定義的變量黑滴。局部變量必須初始化憨募。 形式參數(shù)是局部變量,局部變量的數(shù)據(jù)存在于棧內(nèi)存中袁辈。棧內(nèi)存中的局部變量隨著方法的消失而消失菜谣。 成員變量存儲(chǔ)在堆中的對象里面,由垃圾回收器負(fù)責(zé)回收晚缩。
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 change(int i) {
i = 1234;
}
}
對于以上這段代碼尾膊,date為局部變量,i,d,m,y都是形參為局部變量荞彼,day冈敛,month,year為成員變量鸣皂。下面分析一下代碼執(zhí)行時(shí)候的變化:
- main方法開始執(zhí)行:int date = 9; date局部變量抓谴,基礎(chǔ)類型,引用和值都存在棧中寞缝。
- Test test = new Test();test為對象引用癌压,存在棧中,對象(new Test())存在堆中荆陆。
- test.change(date); i為局部變量滩届,引用和值存在棧中。當(dāng)方法change執(zhí)行完成后被啼,i就會(huì)從棧中消失丐吓。
- BirthDate d1= new BirthDate(7,7,1970); d1為對象引用,存在棧中趟据,對象(new BirthDate())存在堆中,其中d术健,m汹碱,y為局部變量存儲(chǔ)在棧中,且它們的類型為基礎(chǔ)類型荞估,因此它們的數(shù)據(jù)也存儲(chǔ)在棧中咳促。day,month,year為成員變量,它們存儲(chǔ)在堆中(new BirthDate()里面)勘伺。當(dāng)BirthDate構(gòu)造方法執(zhí)行完之后跪腹,d,m,y將從棧中消失。
- main方法執(zhí)行完之后飞醉,date變量冲茸,test,d1引用將從棧中消失,new Test(), new BirthDate()將等待垃圾回收.