真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

Java中內(nèi)存中的Heap、Stack與程序運(yùn)行的關(guān)系-創(chuàng)新互聯(lián)

堆和棧的內(nèi)存管理

讓客戶滿意是我們工作的目標(biāo),不斷超越客戶的期望值來(lái)自于我們對(duì)這個(gè)行業(yè)的熱愛(ài)。我們立志把好的技術(shù)通過(guò)有效、簡(jiǎn)單的方式提供給客戶,將通過(guò)不懈努力成為客戶在信息化領(lǐng)域值得信任、有價(jià)值的長(zhǎng)期合作伙伴,公司提供的服務(wù)項(xiàng)目有:域名注冊(cè)、網(wǎng)站空間、營(yíng)銷(xiāo)軟件、網(wǎng)站建設(shè)、呼和浩特網(wǎng)站維護(hù)、網(wǎng)站推廣。

棧的內(nèi)存管理是順序分配的,而且定長(zhǎng),不存在內(nèi)存回收問(wèn)題;而堆 則是隨機(jī)分配內(nèi)存,不定長(zhǎng)度,存在內(nèi)存分配和回收的問(wèn)題;
堆內(nèi)存和棧內(nèi)存的區(qū)別可以用如下的比喻來(lái)看出:使用堆內(nèi)存就象是自己動(dòng)手做喜歡吃的菜肴,比較麻煩,但是比較符合自己的口味,而且自由度大。使用棧內(nèi)存就象我們?nèi)ワ堭^里吃飯,只管點(diǎn)菜(發(fā)出申請(qǐng))、付錢(qián)和吃(使用),吃飽了就走,不必理會(huì)切菜、洗菜等準(zhǔn)備工作和洗碗、刷鍋等掃尾工作,他的好處是快捷,但是自由度小。操作系統(tǒng)中所說(shuō)的堆內(nèi)存和棧內(nèi)存,在操作上有上述的特點(diǎn),這里的堆內(nèi)存實(shí)際上指的就是(滿足堆內(nèi)存性質(zhì)的)優(yōu)先隊(duì)列的一種數(shù)據(jù)結(jié)構(gòu),第1個(gè)元素有高的優(yōu)先權(quán);棧內(nèi)存實(shí)際上就是滿足先進(jìn)后出的性質(zhì)的數(shù)學(xué)或數(shù)據(jù)結(jié)構(gòu)。

在Java中堆是Java虛擬機(jī)JVM的內(nèi)存數(shù)據(jù)區(qū)。Heap 的管理很復(fù)雜,每次分配不定長(zhǎng)的內(nèi)存空間,專門(mén)用來(lái)保存對(duì)象的實(shí)例(new 創(chuàng)建的對(duì)象和數(shù)組)。在Heap 中分配一定的內(nèi)存來(lái)保存對(duì)象實(shí)例,實(shí)際上也只是保存對(duì)象實(shí)例的屬性值,屬性的類型和對(duì)象本身的類型標(biāo)記等,并不保存對(duì)象的方法(方法是指令,保存在Stack中),在Heap 中分配一定的內(nèi)存保存對(duì)象實(shí)例和對(duì)象的序列化比較類似。而對(duì)象實(shí)例在Heap 中分配好以后,需要在Stack中保存一個(gè)4字節(jié)的Heap內(nèi)存地址,用來(lái)定位該對(duì)象實(shí)例在Heap 中的位置,便于找到該對(duì)象實(shí)例

數(shù)據(jù)類型

Java虛擬機(jī)中,數(shù)據(jù)類型可以分為兩類:基本類型引用類型?;绢愋偷淖兞勘4嬖贾?,即:他代表的值就是數(shù)值本身;而引用類型的變量保存引用值。“引用值”代表了某個(gè)對(duì)象的引用,而不是對(duì)象本身,對(duì)象本身存放在這個(gè)引用值所表示的地址的位置。
基本類型包括:byte,short,int,long,char,float,double,Boolean,returnAddress
引用類型包括:類類型,接口類型和數(shù)組。

Java數(shù)據(jù)類型的關(guān)系如下圖所示:

對(duì)象句柄:如String s = "asdf"; 初始化后的句柄。以及String s; 未初始化的句柄。

在函數(shù)中定義的一些基本類型的變量和對(duì)象的引用變量(對(duì)象句柄)都是在函數(shù)的棧內(nèi)存中分配。當(dāng)在一段代碼塊中定義一個(gè)變量時(shí),java就在棧中為這個(gè)變量分配內(nèi)存空間,當(dāng)超過(guò)變量的作用域后,java會(huì)自動(dòng)釋放掉為該變量分配的內(nèi)存空間,該內(nèi)存空間可以立刻被另作他用。

    堆內(nèi)存用于存放由new創(chuàng)建的對(duì)象和數(shù)組。在堆中分配的內(nèi)存,由java虛擬機(jī)自動(dòng)垃圾回收器來(lái)管理。在堆中產(chǎn)生了一個(gè)數(shù)組或者對(duì)象后,還可以在棧中定義一個(gè)特殊的變量,這個(gè)變量的取值等于數(shù)組或者對(duì)象在堆內(nèi)存中的首地址,在棧中的這個(gè)特殊的變量就變成了數(shù)組或者對(duì)象的引用變量,以后就可以在程序中使用棧內(nèi)存中的引用變量來(lái)訪問(wèn)堆中的數(shù)組或者對(duì)象,引用變量相當(dāng)于為數(shù)組或者對(duì)象起的一個(gè)別名,或者代號(hào)。

    引用變量是普通變量,定義時(shí)在棧中分配內(nèi)存,引用變量在程序運(yùn)行到作用域外釋放。而數(shù)組&對(duì)象本身在堆中分配,即使程序運(yùn)行到使用new產(chǎn)生數(shù)組和對(duì)象的語(yǔ)句所在地代碼塊之外,數(shù)組和對(duì)象本身占用的堆內(nèi)存也不會(huì)被釋放,數(shù)組和對(duì)象在沒(méi)有引用變量指向它的時(shí)候,才變成垃圾,不能再被使用,但是仍然占著內(nèi)存,在隨后的一個(gè)不確定的時(shí)間被垃圾回收器釋放掉。這個(gè)也是java比較占內(nèi)存的主要原因,實(shí)際上,棧中的變量指向堆內(nèi)存中的變量,這就是 Java 中的指針!

Java中所有對(duì)象的存儲(chǔ)空間都是在堆中分配的,但是這個(gè)對(duì)象的引用卻是在棧中分配,也就是說(shuō)在建立一個(gè)對(duì)象時(shí)從兩個(gè)地方都分配內(nèi)存,在堆中分配的內(nèi)存實(shí)際建立這個(gè)對(duì)象,而在棧中分配的內(nèi)存只是一個(gè)指向這個(gè)堆對(duì)象的指針(引用)而已。

堆和棧

堆和棧是程序運(yùn)行的關(guān)鍵,應(yīng)該將其理解清楚。

棧是運(yùn)行時(shí)的單位,而堆是存儲(chǔ)時(shí)的單位。

 棧解決程序的運(yùn)行問(wèn)題,即程序如何執(zhí)行,或者說(shuō)如何處理數(shù)據(jù);堆解決的是數(shù)據(jù)存儲(chǔ)的問(wèn)題,即數(shù)據(jù)怎么放、放在哪兒。
在Java中一個(gè)線程就會(huì)相應(yīng)有一個(gè)線程棧與之對(duì)應(yīng),這點(diǎn)很容易理解,因?yàn)椴煌木€程執(zhí)行邏輯有所不同,因此需要一個(gè)獨(dú)立的線程棧。而堆則是所有線程共享的。棧因?yàn)槭沁\(yùn)行單位,因此里面存儲(chǔ)的信息都是跟當(dāng)前線程(或程序)相關(guān)信息的。包括局部變量、程序運(yùn)行狀態(tài)、方法返回值等等;而堆只負(fù)責(zé)存儲(chǔ)對(duì)象信息。

為什么要把堆和棧區(qū)分出來(lái)呢?棧中不是也可以存儲(chǔ)數(shù)據(jù)嗎?

第一,從軟件設(shè)計(jì)的角度看,棧代表了處理邏輯,而堆代表了數(shù)據(jù)。這樣分開(kāi),使得處理邏輯更為清晰。分而治之的思想。這種隔離、模塊化的思想在軟件設(shè)計(jì)的方方面面都有體現(xiàn)。
第二,堆與棧的分離,使得堆中的內(nèi)容可以被多個(gè)棧共享(也可以理解為多個(gè)線程訪問(wèn)同一個(gè)對(duì)象)。這種共享的收益是很多的。一方面這種共享提供了一種有效的數(shù)據(jù)交互方式(如:共享內(nèi)存),另一方面,堆中的共享常量和緩存可以被所有棧訪問(wèn),節(jié)省了空間。
第三,棧因?yàn)檫\(yùn)行時(shí)的需要,比如保存系統(tǒng)運(yùn)行的上下文,需要進(jìn)行地址段的劃分。由于棧只能向上增長(zhǎng),因此就會(huì)限制住棧存儲(chǔ)內(nèi)容的能力。而堆不同,堆中的對(duì)象是可以根據(jù)需要?jiǎng)討B(tài)增長(zhǎng)的,因此棧和堆的拆分,使得動(dòng)態(tài)增長(zhǎng)成為可能,相應(yīng)棧中只需記錄堆中的一個(gè)地址即可。
第四,面向?qū)ο缶褪嵌押蜅5耐昝澜Y(jié)合。其實(shí),面向?qū)ο蠓绞降某绦蚺c以前結(jié)構(gòu)化的程序在執(zhí)行上沒(méi)有任何區(qū)別。但是,面向?qū)ο蟮囊?,使得?duì)待問(wèn)題的思考方式發(fā)生了改變,而更接近于自然方式的思考。當(dāng)我們把對(duì)象拆開(kāi),你會(huì)發(fā)現(xiàn),對(duì)象的屬性其實(shí)就是數(shù)據(jù),存放在堆中;而對(duì)象的行為(方法),就是運(yùn)行邏輯,放在棧中。我們?cè)诰帉?xiě)對(duì)象的時(shí)候,其實(shí)即編寫(xiě)了數(shù)據(jù)結(jié)構(gòu),也編寫(xiě)的處理數(shù)據(jù)的邏輯。不得不承認(rèn),面向?qū)ο蟮脑O(shè)計(jì),確實(shí)很美。

在Java中,Main函數(shù)就是棧的起始點(diǎn),也是程序的起始點(diǎn)。

 程序要運(yùn)行總是有一個(gè)起點(diǎn)的。同C語(yǔ)言一樣,java中的Main就是那個(gè)起點(diǎn)。無(wú)論什么java程序,找到main就找到了程序執(zhí)行的入口。

堆中存什么?棧中存什么?

堆中存的是對(duì)象。棧中存的是基本數(shù)據(jù)類型堆中對(duì)象的引用。一個(gè)對(duì)象的大小是不可估計(jì)的,或者說(shuō)是可以動(dòng)態(tài)變化的,但是在棧中,一個(gè)對(duì)象只對(duì)應(yīng)了一個(gè)4btye的引用(堆棧分離的好處)。
為什么不把基本類型放堆中呢?因?yàn)槠湔加玫目臻g一般是1~8個(gè)字節(jié)——需要空間比較少,而且因?yàn)槭腔绢愋?,所以不?huì)出現(xiàn)動(dòng)態(tài)增長(zhǎng)的情況——長(zhǎng)度固定,因此棧中存儲(chǔ)就夠了,如果把他存在堆中是沒(méi)有什么意義的(還會(huì)浪費(fèi)空間,后面說(shuō)明)??梢赃@么說(shuō),基本類型和對(duì)象的引用都是存放在棧中,而且都是幾個(gè)字節(jié)的一個(gè)數(shù),因此在程序運(yùn)行時(shí),他們的處理方式是統(tǒng)一的。但是基本類型、對(duì)象引用和對(duì)象本身就有所區(qū)別了,因?yàn)橐粋€(gè)是棧中的數(shù)據(jù)一個(gè)是堆中的數(shù)據(jù)。最常見(jiàn)的一個(gè)問(wèn)題就是,Java中參數(shù)傳遞時(shí)的問(wèn)題。

Java中的參數(shù)傳遞時(shí)傳值呢?還是傳引用?

要說(shuō)明這個(gè)問(wèn)題,先要明確兩點(diǎn):
1.不要試圖與C進(jìn)行類比,Java中沒(méi)有指針的概念
2.程序運(yùn)行永遠(yuǎn)都是在棧中進(jìn)行的,因而參數(shù)傳遞時(shí),只存在傳遞基本類型和對(duì)象引用的問(wèn)題。不會(huì)直接傳對(duì)象本身。

   明確以上兩點(diǎn)后。Java在方法調(diào)用傳遞參數(shù)時(shí),因?yàn)闆](méi)有指針,所以它都是進(jìn)行傳值調(diào)用(這點(diǎn)可以參考C的傳值調(diào)用)。因此,很多書(shū)里面都說(shuō)Java是進(jìn)行傳值調(diào)用,這點(diǎn)沒(méi)有問(wèn)題,而且也簡(jiǎn)化的C中復(fù)雜性。

     但是傳引用的錯(cuò)覺(jué)是如何造成的呢?在運(yùn)行棧中,基本類型和引用的處理是一樣的,都是傳值,所以,如果是傳引用的方法調(diào)用,也同時(shí)可以理解為“傳引用值”的傳值調(diào)用,即引用的處理跟基本類型是完全一樣的。但是當(dāng)進(jìn)入被調(diào)用方法時(shí),被傳遞的這個(gè)引用的值,被程序解釋(或者查找)到堆中的對(duì)象,這個(gè)時(shí)候才對(duì)應(yīng)到真正的對(duì)象。如果此時(shí)進(jìn)行修改,修改的是引用對(duì)應(yīng)的對(duì)象,而不是引用本身,即:修改的是堆中的數(shù)據(jù)。所以這個(gè)修改是可以保持的了。
     對(duì)象,從某種意義上說(shuō),是由基本類型組成的??梢园岩粋€(gè)對(duì)象看作為一棵樹(shù),對(duì)象的屬性如果還是對(duì)象,則還是一顆樹(shù)(即非葉子節(jié)點(diǎn)),基本類型則為樹(shù)的葉子節(jié)點(diǎn)。程序參數(shù)傳遞時(shí),被傳遞的值本身都是不能進(jìn)行修改的,但是,如果這個(gè)值是一個(gè)非葉子節(jié)點(diǎn)(即一個(gè)對(duì)象引用),則可以修改這個(gè)節(jié)點(diǎn)下面的所有內(nèi)容。
     堆和棧中,棧是程序運(yùn)行最根本的東西。程序運(yùn)行可以沒(méi)有堆,但是不能沒(méi)有棧。而堆是為棧進(jìn)行數(shù)據(jù)存儲(chǔ)服務(wù),說(shuō)白了堆就是一塊共享的內(nèi)存。不過(guò),正是因?yàn)槎押蜅5姆蛛x的思想,才使得Java的垃圾回收成為可能。
      Java中,棧的大小通過(guò)-Xss來(lái)設(shè)置,當(dāng)棧中存儲(chǔ)數(shù)據(jù)比較多時(shí),需要適當(dāng)調(diào)大這個(gè)值,否則會(huì)出現(xiàn)java.lang.StackOverflowError異常。常見(jiàn)的出現(xiàn)這個(gè)異常的是無(wú)法返回的遞歸,因?yàn)榇藭r(shí)棧中保存的信息都是方法返回的記錄點(diǎn)。

Java對(duì)象的大小

基本數(shù)據(jù)的類型的大小是固定的,這里就不多說(shuō)了。對(duì)于非基本類型的Java對(duì)象,其大小就值得商榷。
在Java中,一個(gè)空Object對(duì)象的大小是8byte,這個(gè)大小只是保存堆中一個(gè)沒(méi)有任何屬性的對(duì)象的大小。看下面語(yǔ)句:
Object ob = new Object();
這樣在程序中完成了一個(gè)Java對(duì)象的生命,但是它所占的空間為:4byte+8byte。4byte是上面部分所說(shuō)的Java棧中保存引用的所需要的空間。而那8byte則是Java堆中對(duì)象的信息。因?yàn)樗械腏ava非基本類型的對(duì)象都需要默認(rèn)繼承Object對(duì)象,因此不論什么樣的Java對(duì)象,其大小都必須是大于8byte。
有了Object對(duì)象的大小,我們就可以計(jì)算其他對(duì)象的大小了。
Class NewObject {
int count;
boolean flag;
Object ob;
}
其大小為:空對(duì)象大小(8byte)+int大小(4byte)+Boolean大小(1byte)+空Object引用的大小(4byte)=17byte。但是因?yàn)镴ava在對(duì)對(duì)象內(nèi)存分配時(shí)都是以8的整數(shù)倍來(lái)分,因此大于17byte的最接近8的整數(shù)倍的是24,因此此對(duì)象的大小為24byte。

基本數(shù)據(jù)類型

包裝類 (是否實(shí)現(xiàn)了常量池技術(shù))

 byte

 Byte     是

 boolean

 Boolean       是

 short

 Short           是

 char

 Character     是

 int

 Integer        是

 long

 Long           是

 float

 Float         否

 double

 Double      否

常量池技術(shù)詳解

  1) java中基本類型的包裝類的大部分都實(shí)現(xiàn)了常量池技術(shù),這些類是Byte,Short,Integer,Long,Character,Boolean,另外兩種浮點(diǎn)數(shù)類型的包裝類則沒(méi)有實(shí)現(xiàn)。

  2) Byte,Short,Integer,Long,Character這5種整型的包裝類也只是在對(duì)應(yīng)值小于等于127時(shí)才可使用對(duì)象池

  下面我們主要使用Long類型來(lái)進(jìn)行講解吧。

 首先我們先寫(xiě)一個(gè)測(cè)試類:

 LongTypeTest.java

package Test;

public class LongTypeTest {
    
public static void main(String[] args) {
long longParam = 50L;
        Long longParam2= 50L;
    }
}
View Code

我們通過(guò)javac命令編譯后,用DJ Java Decompiler打開(kāi),選擇View->Bytecode View,得到如下:

注意:反編譯后也可以用命令javap -c LongTypeTest > LongTypeTest.bc,在CMD下用type LongTypeTest.bc查看也可以。

 1 // Decompiled by DJ v3.12.12.96 Copyright 2011 Atanas Neshkov  Date: 2013/9/15 17:06:18
 2 // Home Page:http://members.fortunecity.com/neshkov/dj.htmlhttp://www.neshkov.com/dj.html - Check often for new version!
 3 // Decompiler options: packimports(3) disassembler 
 4 // Source File Name:   LongTypeTest.java 5 
 6 package Test;
 7 
 8 
 9 public class LongTypeTest
10 {
11 
12   public LongTypeTest()
13     {
14   //    0    0:aload_0         
15   //    1    1:invokespecial   #8   
16   //    2    4:return17     }
18 
19   public static void main(String args[])
20     {
21   //    0    0:ldc2w           #16  
22   //    1    3:lstore_1        
23   //    2    4:ldc2w           #16  
24   //    3    7:invokestatic    #18  
25   //    4   10:astore_3        
26   //    5   11:return27     }
28 }
View Code

 從第24行,我們可以看到,使用包裝類初始化的時(shí)候,調(diào)用的是Long類中的valueOf方法,下面我們看看,Long類中的該方法是怎樣的。

1 public static Long valueOf(long l) {
2 final int offset = 128;
3 //當(dāng) l >= -128 && l <= 127 時(shí),返回常量池中緩存的數(shù)據(jù)4 if (l >= -128 && l <= 127) { // will cache5  return LongCache.cache[(int)l + offset];
6         }
7 //否則初始化一個(gè)新的Long對(duì)象8 return new Long(l);
9     }
View Code

從代碼中看出,當(dāng) l 的值小于127的時(shí)候,將會(huì)調(diào)用LongCache.cache()中獲取常量池中的數(shù)值。其中,LongCache是一個(gè)內(nèi)部類

 1 //Long類中的私有類 2   private static class LongCache {
 3   //私有的構(gòu)造方法,不允許初始化 4 private LongCache(){}
 5   //static final類型,它的值在編譯期間將會(huì)確定下來(lái)并且被存儲(chǔ)到常量池中 6 static final Long cache[] = new Long[-(-128) + 127 + 1];
 7   //靜態(tài)代碼塊,為cache數(shù)組賦值 8 static {
 9  for(int i = 0; i < cache.length; i++)
10                 cache[i] = new Long(i - 128);
11         }
12     }
View Code

其他Byte,Short,Integer,Long,Character,Boolean都是差不多的,具體就不在此重復(fù)講了。

我們?cè)贒ouble中的valueOf中我們可以看到源代碼是這樣子的:

1public static Double valueOf(double d) {
2 //直接初始化并返回一個(gè)Double對(duì)象3 return new Double(d);
4  }
View Code

 Float亦是如此。

這里需要注意一下基本類型的包裝類型的大小。因?yàn)檫@種包裝類型已經(jīng)成為對(duì)象了,因此需要把他們作為對(duì)象來(lái)看待。包裝類型的大小至少是12byte(聲明一個(gè)空Object至少需要的空間),而且12byte沒(méi)有包含任何有效信息,同時(shí),因?yàn)镴ava對(duì)象大小是8的整數(shù)倍,因此一個(gè)基本類型包裝類的大小至少是16byte。這個(gè)內(nèi)存占用是很恐怖的,它是使用基本類型的N倍(N>2),有些類型的內(nèi)存占用更是夸張(隨便想下就知道了)。因此,可能的話應(yīng)盡量少使用包裝類。在JDK5.0以后,因?yàn)榧尤肓俗詣?dòng)類型裝換,因此,Java虛擬機(jī)會(huì)在存儲(chǔ)方面進(jìn)行相應(yīng)的優(yōu)化。

  //int類型會(huì)自動(dòng)轉(zhuǎn)換為Integer類型  int m = 12;
    Integer in= m;
//Integer類型會(huì)自動(dòng)轉(zhuǎn)換為int類型  int n = in;

引用類型

對(duì)象引用類型分為強(qiáng)引用軟引用、弱引用虛引用。
強(qiáng)引用:就是我們一般聲明對(duì)象是時(shí)虛擬機(jī)生成的引用,強(qiáng)引用環(huán)境下,垃圾回收時(shí)需要嚴(yán)格判斷當(dāng)前對(duì)象是否被強(qiáng)引用,如果被強(qiáng)引用,則不會(huì)被垃圾回收
軟引用:軟引用一般被做為緩存來(lái)使用。與強(qiáng)引用的區(qū)別是,軟引用在垃圾回收時(shí),虛擬機(jī)會(huì)根據(jù)當(dāng)前系統(tǒng)的剩余內(nèi)存來(lái)決定是否對(duì)軟引用進(jìn)行回收。如果剩余內(nèi)存比較緊張,則虛擬機(jī)會(huì)回收軟引用所引用的空間;如果剩余內(nèi)存相對(duì)富裕,則不會(huì)進(jìn)行回收。換句話說(shuō),虛擬機(jī)在發(fā)生OutOfMemory時(shí),肯定是沒(méi)有軟引用存在的。
弱引用:弱引用與軟引用類似,都是作為緩存來(lái)使用。但與軟引用不同,弱引用在進(jìn)行垃圾回收時(shí),是一定會(huì)被回收掉的,因此其生命周期只存在于一個(gè)垃圾回收周期內(nèi)。
強(qiáng)引用不用說(shuō),我們系統(tǒng)一般在使用時(shí)都是用的強(qiáng)引用。而“軟引用”和“弱引用”比較少見(jiàn)。他們一般被作為緩存使用,而且一般是在內(nèi)存大小比較受限的情況下做為緩存。因?yàn)槿绻麅?nèi)存足夠大的話,可以直接使用強(qiáng)引用作為緩存即可,同時(shí)可控性更高。因而,他們常見(jiàn)的是被使用在桌面應(yīng)用系統(tǒng)的緩存。


網(wǎng)頁(yè)標(biāo)題:Java中內(nèi)存中的Heap、Stack與程序運(yùn)行的關(guān)系-創(chuàng)新互聯(lián)
標(biāo)題網(wǎng)址:http://weahome.cn/article/ddgjos.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部