日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

Java中内存中的Heap、Stack与程序运行的关系

發布時間:2023/12/13 java 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java中内存中的Heap、Stack与程序运行的关系 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

堆和棧的內存管理

棧的內存管理是順序分配的,而且定長,不存在內存回收問題;而堆 則是隨機分配內存,不定長度,存在內存分配和回收的問題;
堆內存和棧內存的區別可以用如下的比喻來看出:使用堆內存就象是自己動手做喜歡吃的菜肴,比較麻煩,但是比較符合自己的口味,而且自由度大。使用棧內存就象我們去飯館里吃飯,只管點菜(發出申請)、付錢和吃(使用),吃飽了就走,不必理會切菜、洗菜等準備工作和洗碗、刷鍋等掃尾工作,他的好處是快捷,但是自由度小。操作系統中所說的堆內存和棧內存,在操作上有上述的特點,這里的堆內存實際上指的就是(滿足堆內存性質的)優先隊列的一種數據結構,第1個元素有最高的優先權;棧內存實際上就是滿足先進后出的性質的數學或數據結構。

在Java中堆是Java虛擬機JVM的內存數據區。Heap 的管理很復雜,每次分配不定長的內存空間,專門用來保存對象的實例(new 創建的對象和數組)。在Heap 中分配一定的內存來保存對象實例,實際上也只是保存對象實例的屬性值,屬性的類型和對象本身的類型標記等,并不保存對象的方法(方法是指令,保存在Stack中)在Heap 中分配一定的內存保存對象實例和對象的序列化比較類似。而對象實例在Heap 中分配好以后,需要在Stack中保存一個4字節的Heap內存地址,用來定位該對象實例在Heap 中的位置,便于找到該對象實例。

數據類型

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

Java數據類型的關系如下圖所示:

?


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

?

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

???? 內存用于存放由new創建的對象和數組。在堆中分配的內存,由java虛擬機自動垃圾回收器來管理。在堆中產生了一個數組或者對象后,還可以在棧中定義一個特殊的變量,這個變量的取值等于數組或者對象在堆內存中的首地址,在棧中的這個特殊的變量就變成了數組或者對象的引用變量,以后就可以在程序中使用棧內存中的引用變量來訪問堆中的數組或者對象,引用變量相當于為數組或者對象起的一個別名,或者代號。

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

????? Java中所有對象的存儲空間都是在堆中分配的,但是這個對象的引用卻是在棧中分配,也就是說在建立一個對象時從兩個地方都分配內存,在堆中分配的內存實際建立這個對象,而在棧中分配的內存只是一個指向這個堆對象的指針(引用)而已。

?

堆和棧

堆和棧是程序運行的關鍵,應該將其理解清楚。

棧是運行時的單位,而堆是存儲時的單位。

?棧解決程序的運行問題,即程序如何執行,或者說如何處理數據;堆解決的是數據存儲的問題,即數據怎么放、放在哪兒。
在Java中一個線程就會相應有一個線程棧與之對應,這點很容易理解,因為不同的線程執行邏輯有所不同,因此需要一個獨立的線程棧。而堆則是所有線程共享的。棧因為是運行單位,因此里面存儲的信息都是跟當前線程(或程序)相關信息的。包括局部變量、程序運行狀態、方法返回值等等;而堆只負責存儲對象信息。

為什么要把堆和棧區分出來呢?棧中不是也可以存儲數據嗎?


第一,從軟件設計的角度看,棧代表了處理邏輯,而堆代表了數據。這樣分開,使得處理邏輯更為清晰。分而治之的思想。這種隔離、模塊化的思想在軟件設計的方方面面都有體現。
第二,堆與棧的分離,使得堆中的內容可以被多個棧共享(也可以理解為多個線程訪問同一個對象)。這種共享的收益是很多的。一方面這種共享提供了一種有效的數據交互方式(如:共享內存),另一方面,堆中的共享常量和緩存可以被所有棧訪問,節省了空間。
第三,棧因為運行時的需要,比如保存系統運行的上下文,需要進行地址段的劃分。由于棧只能向上增長,因此就會限制住棧存儲內容的能力。而堆不同,堆中的對象是可以根據需要動態增長的,因此棧和堆的拆分,使得動態增長成為可能,相應棧中只需記錄堆中的一個地址即可。
第四,面向對象就是堆和棧的完美結合。其實,面向對象方式的程序與以前結構化的程序在執行上沒有任何區別。但是,面向對象的引入,使得對待問題的思考方式發生了改變,而更接近于自然方式的思考。當我們把對象拆開,你會發現,對象的屬性其實就是數據,存放在堆中;而對象的行為(方法),就是運行邏輯,放在棧中。我們在編寫對象的時候,其實即編寫了數據結構,也編寫的處理數據的邏輯。不得不承認,面向對象的設計,確實很美。

在Java中,Main函數就是棧的起始點,也是程序的起始點

? 程序要運行總是有一個起點的。同C語言一樣,java中的Main就是那個起點。無論什么java程序,找到main就找到了程序執行的入口。

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

?

堆中存的是對象棧中存的是基本數據類型堆中對象的引用。一個對象的大小是不可估計的,或者說是可以動態變化的,但是在棧中,一個對象只對應了一個4btye的引用(堆棧分離的好處)。
為什么不把基本類型放堆中呢?因為其占用的空間一般是1~8個字節——需要空間比較少,而且因為是基本類型,所以不會出現動態增長的情況——長度固定,因此棧中存儲就夠了,如果把他存在堆中是沒有什么意義的(還會浪費空間,后面說明)。可以這么說,基本類型和對象的引用都是存放在棧中,而且都是幾個字節的一個數,因此在程序運行時,他們的處理方式是統一的。但是基本類型、對象引用和對象本身就有所區別了,因為一個是棧中的數據一個是堆中的數據。最常見的一個問題就是,Java中參數傳遞時的問題。

Java中的參數傳遞時傳值呢?還是傳引用?

要說明這個問題,先要明確兩點:
1.不要試圖與C進行類比,Java中沒有指針的概念
2.程序運行永遠都是在棧中進行的,因而參數傳遞時,只存在傳遞基本類型和對象引用的問題。不會直接傳對象本身。


??? 明確以上兩點后。Java在方法調用傳遞參數時,因為沒有指針,所以它都是進行傳值調用(這點可以參考C的傳值調用)。因此,很多書里面都說Java是進行傳值調用,這點沒有問題,而且也簡化的C中復雜性。

????? 但是傳引用的錯覺是如何造成的呢?在運行棧中,基本類型和引用的處理是一樣的,都是傳值,所以,如果是傳引用的方法調用,也同時可以理解為“傳引用值”的傳值調用,即引用的處理跟基本類型是完全一樣的。但是當進入被調用方法時,被傳遞的這個引用的值,被程序解釋(或者查找)到堆中的對象,這個時候才對應到真正的對象。如果此時進行修改,修改的是引用對應的對象,而不是引用本身,即:修改的是堆中的數據。所以這個修改是可以保持的了。
????? 對象,從某種意義上說,是由基本類型組成的??梢园岩粋€對象看作為一棵樹,對象的屬性如果還是對象,則還是一顆樹(即非葉子節點),基本類型則為樹的葉子節點。程序參數傳遞時,被傳遞的值本身都是不能進行修改的,但是,如果這個值是一個非葉子節點(即一個對象引用),則可以修改這個節點下面的所有內容。?
????? 堆和棧中,棧是程序運行最根本的東西。程序運行可以沒有堆,但是不能沒有棧。而堆是為棧進行數據存儲服務,說白了堆就是一塊共享的內存。不過,正是因為堆和棧的分離的思想,才使得Java的垃圾回收成為可能。
?????? Java中,棧的大小通過-Xss來設置,當棧中存儲數據比較多時,需要適當調大這個值,否則會出現java.lang.StackOverflowError異常。常見的出現這個異常的是無法返回的遞歸,因為此時棧中保存的信息都是方法返回的記錄點。

Java對象的大小

基本數據的類型的大小是固定的,這里就不多說了。對于非基本類型的Java對象,其大小就值得商榷。
在Java中,一個空Object對象的大小是8byte,這個大小只是保存堆中一個沒有任何屬性的對象的大小??聪旅嬲Z句:
Object ob = new Object();
這樣在程序中完成了一個Java對象的生命,但是它所占的空間為:4byte+8byte。4byte是上面部分所說的Java棧中保存引用的所需要的空間。而那8byte則是Java堆中對象的信息。因為所有的Java非基本類型的對象都需要默認繼承Object對象,因此不論什么樣的Java對象,其大小都必須是大于8byte。
有了Object對象的大小,我們就可以計算其他對象的大小了。
Class NewObject {
int count;
boolean flag;
Object ob;
}
其大小為:空對象大小(8byte)+int大小(4byte)+Boolean大小(1byte)+空Object引用的大小(4byte)=17byte。但是因為Java在對對象內存分配時都是以8的整數倍來分,因此大于17byte的最接近8的整數倍的是24,因此此對象的大小為24byte。

基本數據類型

包裝類 (是否實現了常量池技術)

?byte

?Byte?    是

?boolean

?Boolean??????? 是

?short

?Short??????????? 是

?char

?Character????? 是

?int

?Integer???????? 是

?long

?Long??????????? 是

?float

?Float????????? 否

?double

?Double?????? 否

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

?

常量池技術詳解

?? 1) java中基本類型的包裝類的大部分都實現了常量池技術,這些類是Byte,Short,Integer,Long,Character,Boolean,另外兩種浮點數類型的包裝類則沒有實現。

?? 2) Byte,Short,Integer,Long,Character這5種整型的包裝類也只是在對應值小于等于127時才可使用對象池

?? 下面我們主要使用Long類型來進行講解吧。

 首先我們先寫一個測試類:

 LongTypeTest.java

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

我們通過javac命令編譯后,用DJ Java Decompiler打開,選擇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.html http://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 <Method void Object()> 16 // 2 4:return 17 } 18 19 public static void main(String args[]) 20 { 21 // 0 0:ldc2w #16 <Long 50L> 22 // 1 3:lstore_1 23 // 2 4:ldc2w #16 <Long 50L> 24 // 3 7:invokestatic #18 <Method Long Long.valueOf(long)> 25 // 4 10:astore_3 26 // 5 11:return 27 } 28 } View Code

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

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

從代碼中看出,當 l 的值小于127的時候,將會調用LongCache.cache()中獲取常量池中的數值。其中,LongCache是一個內部類

1 //Long類中的私有類 2 private static class LongCache { 3 //私有的構造方法,不允許初始化 4 private LongCache(){} 5 //static final類型,它的值在編譯期間將會確定下來并且被存儲到常量池中 6 static final Long cache[] = new Long[-(-128) + 127 + 1]; 7 //靜態代碼塊,為cache數組賦值 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都是差不多的,具體就不在此重復講了。

我們在Double中的valueOf中我們可以看到源代碼是這樣子的:

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

?Float亦是如此。


這里需要注意一下基本類型的包裝類型的大小。因為這種包裝類型已經成為對象了,因此需要把他們作為對象來看待。包裝類型的大小至少是12byte(聲明一個空Object至少需要的空間),而且12byte沒有包含任何有效信息,同時,因為Java對象大小是8的整數倍,因此一個基本類型包裝類的大小至少是16byte這個內存占用是很恐怖的,它是使用基本類型的N倍(N>2),有些類型的內存占用更是夸張(隨便想下就知道了)。因此,可能的話應盡量少使用包裝類。在JDK5.0以后,因為加入了自動類型裝換,因此,Java虛擬機會在存儲方面進行相應的優化。

//int類型會自動轉換為Integer類型int m = 12;Integer in = m;//Integer類型會自動轉換為int類型int n = in;

引用類型


對象引用類型分為強引用、軟引用弱引用虛引用。
強引用:就是我們一般聲明對象是時虛擬機生成的引用,強引用環境下,垃圾回收時需要嚴格判斷當前對象是否被強引用,如果被強引用,則不會被垃圾回收
軟引用:軟引用一般被做為緩存來使用。與強引用的區別是,軟引用在垃圾回收時,虛擬機會根據當前系統的剩余內存來決定是否對軟引用進行回收。如果剩余內存比較緊張,則虛擬機會回收軟引用所引用的空間;如果剩余內存相對富裕,則不會進行回收。換句話說,虛擬機在發生OutOfMemory時,肯定是沒有軟引用存在的。
弱引用:弱引用與軟引用類似,都是作為緩存來使用。但與軟引用不同,弱引用在進行垃圾回收時,是一定會被回收掉的,因此其生命周期只存在于一個垃圾回收周期內。
強引用不用說,我們系統一般在使用時都是用的強引用。而“軟引用”和“弱引用”比較少見。他們一般被作為緩存使用,而且一般是在內存大小比較受限的情況下做為緩存。因為如果內存足夠大的話,可以直接使用強引用作為緩存即可,同時可控性更高。因而,他們常見的是被使用在桌面應用系統的緩存。

?

轉載于:https://www.cnblogs.com/shudonghe/p/3264222.html

總結

以上是生活随笔為你收集整理的Java中内存中的Heap、Stack与程序运行的关系的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。