❶ java虛擬機內存分配與回收機制
[轉帖] Java 中的堆和棧
簡單的說:
Java把內存劃分成兩種:一種是棧內存,一種是堆內存。
在函數中定義的一些基本類型的變數和對象的引用變數都在函數的棧內存中分配。
當在一段代碼塊定義一個變數時,Java就在棧中為這個變數分配內存空間,當超過變數的作用域後,Java會自動釋放掉為該變數所分配的內存空間,該內存空間可以立即被另作他用。
堆內存用來存放由new創建的對象和數組。
在堆中分配的內存,由Java虛擬機的自動垃圾回收器來管理。
在堆中產生了一個數組或對象後,還可以在棧中定義一個特殊的變數,讓棧中這個變數的取值等於數組或對象在堆內存中的首地址,棧中的這個變數就成了數組或對象的引用變數。
引用變數就相當於是為數組或對象起的一個名稱,以後就可以在程序中使用棧中的引用變數來訪問堆中的數組或對象。
具體的說:
棧與堆都是Java用來在Ram中存放數據的地方。與C++不同,Java自動管理棧和堆,程序員不能直接地設置棧或堆。
Java的堆是一個運行時數據區,類的(對象從中分配空間。這些對象通過new、newarray、anewarray和multianewarray等 指令建立,它們不需要程序代碼來顯式的釋放。堆是由垃圾回收來負責的,堆的優勢是可以動態地分配內存大小,生存期也不必事先告訴編譯器,因為它是在運行時 動態分配內存的,Java的垃圾收集器會自動收走這些不再使用的數據。但缺點是,由於要在運行時動態分配內存,存取速度較慢。
棧的優勢是,存取速度比堆要快,僅次於寄存器,棧數據可以共享。但缺點是,存在棧中的數據大小與生存期必須是確定的,缺乏靈活性。棧中主要存放一些基本 類型的變數(,int, short, long, byte, float, double, boolean, char)和對象句柄。
棧有一個很重要的特殊性,就是存在棧中的數據可以共享。假設我們同時定義:
int a = 3;
int b = 3;
編譯器先處理int a = 3;首先它會在棧中創建一個變數為a的引用,然後查找棧中是否有3這個值,如果沒找到,就將3存放進來,然後將a指向3。接著處理int b = 3;在創建完b的引用變數後,因為在棧中已經有3這個值,便將b直接指向3。這樣,就出現了a與b同時均指向3的情況。這時,如果再令a=4;那麼編譯器 會重新搜索棧中是否有4值,如果沒有,則將4存放進來,並令a指向4;如果已經有了,則直接將a指向這個地址。因此a值的改變不會影響到b的值。要注意這 種數據的共享與兩個對象的引用同時指向一個對象的這種共享是不同的,因為這種情況a的修改並不會影響到b, 它是由編譯器完成的,它有利於節省空間。而一個對象引用變數修改了這個對象的內部狀態,會影響到另一個對象引用變數。
String是一個特殊的包裝類數據。可以用:
String str = new String("abc");
String str = "abc";
兩種的形式來創建,第一種是用new()來新建對象的,它會在存放於堆中。每調用一次就會創建一個新的對象。
而第二種是先在棧中創建一個對String類的對象引用變數str,然後查找棧中有沒有存放"abc",如果沒有,則將"abc"存放進棧,並令str指向」abc」,如果已經有」abc」 則直接令str指向「abc」。
比較類裡面的數值是否相等時,用equals()方法;當測試兩個包裝類的引用是否指向同一個對象時,用==,下面用例子說明上面的理論。
String str1 = "abc";
String str2 = "abc";
System.out.println(str1==str2); //true
可以看出str1和str2是指向同一個對象的。
String str1 =new String ("abc");
String str2 =new String ("abc");
System.out.println(str1==str2); // false
用new的方式是生成不同的對象。每一次生成一個。
因此用第二種方式創建多個」abc」字元串,在內存中其實只存在一個對象而已. 這種寫法有利與節省內存空間. 同時它可以在一定程度上提高程序的運行速度,因為JVM會自動根據棧中數據的實際情況來決定是否有必要創建新對象。而對於String str = new String("abc");的代碼,則一概在堆中創建新對象,而不管其字元串值是否相等,是否有必要創建新對象,從而加重了程序的負擔。
另一方面, 要注意: 我們在使用諸如String str = "abc";的格式定義類時,總是想當然地認為,創建了String類的對象str。擔心陷阱!對象可能並沒有被創建!而可能只是指向一個先前已經創建的 對象。只有通過new()方法才能保證每次都創建一個新的對象。 由於String類的immutable性質,當String變數需要經常變換其值時,應該考慮使用StringBuffer類,以提高程序效率。
java中內存分配策略及堆和棧的比較
2.1 內存分配策略
按照編譯原理的觀點,程序運行時的內存分配有三種策略,分別是靜態的,棧式的,和堆式的.
靜態存儲分配是指在編譯時就能確定每個數據目標在運行時刻的存儲空間需求,因而在編譯時就可以給他們分配固定的內存空間.這種分配策略要求程序代碼中不允 許有可變數據結構(比如可變數組)的存在,也不允許有嵌套或者遞歸的結構出現,因為它們都會導致編譯程序無法計算準確的存儲空間需求.
棧式存儲分配也可稱為動態存儲分配,是由一個類似於堆棧的運行棧來實現的.和靜態存儲分配相反,在棧式存儲方案中,程序對數據區的需求在編譯時是完全未知 的,只有到運行的時候才能夠知道,但是規定在運行中進入一個程序模塊時,必須知道該程序模塊所需的數據區大小才能夠為其分配內存.和我們在數據結構所熟知 的棧一樣,棧式存儲分配按照先進後出的原則進行分配。
靜態存儲分配要求在編譯時能知道所有變數的存儲要求,棧式存儲分配要求在過程的入口處必須知道所有的存儲要求,而堆式存儲分配則專門負責在編譯時或運行時 模塊入口處都無法確定存儲要求的數據結構的內存分配,比如可變長度串和對象實例.堆由大片的可利用塊或空閑塊組成,堆中的內存可以按照任意順序分配和釋 放.
2.2 堆和棧的比較
上面的定義從編譯原理的教材中總結而來,除靜態存儲分配之外,都顯得很呆板和難以理解,下面撇開靜態存儲分配,集中比較堆和棧:
從堆和棧的功能和作用來通俗的比較,堆主要用來存放對象的,棧主要是用來執行程序的.而這種不同又主要是由於堆和棧的特點決定的:
在編程中,例如C/C++中,所有的方法調用都是通過棧來進行的,所有的局部變數,形式參數都是從棧中分配內存空間的。實際上也不是什麼分配,只是從棧頂 向上用就行,就好像工廠中的傳送帶(conveyor belt)一樣,Stack Pointer會自動指引你到放東西的位置,你所要做的只是把東西放下來就行.退出函數的時候,修改棧指針就可以把棧中的內容銷毀.這樣的模式速度最快, 當然要用來運行程序了.需要注意的是,在分配的時候,比如為一個即將要調用的程序模塊分配數據區時,應事先知道這個數據區的大小,也就說是雖然分配是在程 序運行時進行的,但是分配的大小多少是確定的,不變的,而這個"大小多少"是在編譯時確定的,不是在運行時.
堆是應用程序在運行的時候請求操作系統分配給自己內存,由於從操作系統管理的內存分配,所以在分配和銷毀時都要佔用時間,因此用堆的效率非常低.但是堆的 優點在於,編譯器不必知道要從堆里分配多少存儲空間,也不必知道存儲的數據要在堆里停留多長的時間,因此,用堆保存數據時會得到更大的靈活性。事實上,面 向對象的多態性,堆內存分配是必不可少的,因為多態變數所需的存儲空間只有在運行時創建了對象之後才能確定.在C++中,要求創建一個對象時,只需用 new命令編制相關的代碼即可。執行這些代碼時,會在堆里自動進行數據的保存.當然,為達到這種靈活性,必然會付出一定的代價:在堆里分配存儲空間時會花 掉更長的時間!這也正是導致我們剛才所說的效率低的原因,看來列寧同志說的好,人的優點往往也是人的缺點,人的缺點往往也是人的優點(暈~).
2.3 JVM中的堆和棧
JVM是基於堆棧的虛擬機.JVM為每個新創建的線程都分配一個堆棧.也就是說,對於一個Java程序來說,它的運行就是通過對堆棧的操作來完成的。堆棧以幀為單位保存線程的狀態。JVM對堆棧只進行兩種操作:以幀為單位的壓棧和出棧操作。
我們知道,某個線程正在執行的方法稱為此線程的當前方法.我們可能不知道,當前方法使用的幀稱為當前幀。當線程激活一個Java方法,JVM就會在線程的 Java堆棧里新壓入一個幀。這個幀自然成為了當前幀.在此方法執行期間,這個幀將用來保存參數,局部變數,中間計算過程和其他數據.這個幀在這里和編譯 原理中的活動紀錄的概念是差不多的.
從Java的這種分配機制來看,堆棧又可以這樣理解:堆棧(Stack)是操作系統在建立某個進程時或者線程(在支持多線程的操作系統中是線程)為這個線程建立的存儲區域,該區域具有先進後出的特性。
每一個Java應用都唯一對應一個JVM實例,每一個實例唯一對應一個堆。應用程序在運行中所創建的所有類實例或數組都放在這個堆中,並由應用所有的線程 共享.跟C/C++不同,Java中分配堆內存是自動初始化的。Java中所有對象的存儲空間都是在堆中分配的,但是這個對象的引用卻是在堆棧中分配,也 就是說在建立一個對象時從兩個地方都分配內存,在堆中分配的內存實際建立這個對象,而在堆棧中分配的內存只是一個指向這個堆對象的指針(引用)而已。
轉自:http://www.anyuok.cn/showbbs.asp?bd=22&id=142&totable=1
呵呵,在看到你的問題前10分鍾才看的這個文章...
❷ Java垃圾回收:GC在什麼時候對什麼做了什麼
GC在什麼時候對什麼做了什麼?
要回答這個問題,先了解下GC的發展史、jvm運行時數據區的劃分、jvm內存分配策略、jvm垃圾收集演算法等知識。
先說下jvm運行時數據的劃分,粗暴的分可以分為堆區(Heap)和棧區(Stack),但jvm的分法實際上比這復雜得多,大概分為下面幾塊:
1、程序計數器(Program Conuter Register)
程序計數器是一塊較小的內存空間,它是當前線程執行位元組碼的行號指示器,位元組碼解釋工作器就是通過改變這個計數器的值來選取下一條需要執行的指令。它是線程私有的內存,也是唯一一個沒有OOM異常的區域。
2、Java虛擬機棧區(Java Virtual Machine Stacks)
也就是通常所說的棧區,它描述的是Java方法執行的內存模型,每個方法被執行的時候都創建一個棧幀(Stack Frame),用於存儲局部變數表、操作數棧、動態鏈接、方法出口等。每個方法被調用到完成,相當於一個棧幀在虛擬機棧中從入棧到出棧的過程。此區域也是線程私有的內存,可能拋出兩種異常:如果線程請求的棧深度大於虛擬機允許的深度將拋出StackOverflowError;如果虛擬機棧可以動態的擴展,擴展到無法動態的申請到足夠的內存時會拋出OOM異常。
3、本地方法棧(Native Method Stacks)
本地方法棧與虛擬機棧發揮的作用非常相似,區別就是虛擬機棧為虛擬機執行Java方法,本地方法棧則是為虛擬機使用到的Native方法服務。
4、堆區(Heap)
所有對象實例和數組都在堆區上分配,堆區是GC主要管理的區域。堆區還可以細分為新生代、老年代,新生代還分為一個Eden區和兩個Survivor區。此塊內存為所有線程共享區域,當堆中沒有足夠內存完成實例分配時會拋出OOM異常。
5、方法區(Method Area)
方法區也是所有線程共享區,用於存儲已被虛擬機載入的類信息、常量、靜態變數、即時編譯後的代碼等數據。GC在這個區域很少出現,這個區域內存回收的目標主要是對常量池的回收和類型的卸載,回收的內存比較少,所以也有稱這個區域為永久代(Permanent Generation)的。當方法區無法滿足內存分配時拋出OOM異常。
6、運行時常量池(Runtime Constant Pool)
運行時常量池是方法區的一部分,用於存放編譯期生成的各種字面量和符號引用。
垃圾收集(Garbage Collection)並不是Java獨有的,最早是出現在Lisp語言中,它做的事就是自動管理內存,也就是下面三個問題:
1、什麼時候回收
2、哪些內存需要回收
3、如何回收
1、什麼時候回收?
上面說到GC經常發生的區域是堆區,堆區還可以細分為新生代、老年代,新生代還分為一個Eden區和兩個Survivor區。
1.1 對象優先在Eden中分配,當Eden中沒有足夠空間時,虛擬機將發生一次Minor GC,因為Java大多數對象都是朝生夕滅,所以Minor GC非常頻繁,而且速度也很快;
1.2 Full GC,發生在老年代的GC,當老年代沒有足夠的空間時即發生Full GC,發生Full GC一般都會有一次Minor GC。大對象直接進入老年代,如很長的字元串數組,虛擬機提供一個-XX:PretenureSizeThreadhold參數,令大於這個參數值的對象直接在老年代中分配,避免在Eden區和兩個Survivor區發生大量的內存拷貝;
1.3 發生Minor GC時,虛擬機會檢測之前每次晉升到老年代的平均大小是否大於老年代的剩餘空間大小,如果大於,則進行一次Full GC,如果小於,則查看HandlePromotionFailure設置是否允許擔保失敗,如果允許,那隻會進行一次Minor GC,如果不允許,則改為進行一次Full GC。
2、哪些內存需要回收
jvm對不可用的對象進行回收,哪些對象是可用的,哪些是不可用的?Java並不是採用引用計數演算法來判定對象是否可用,而是採用根搜索演算法(GC Root Tracing),當一個對象到GC Roots沒有任何引用相連接,用圖論的來說就是從GC Roots到這個對象不可達,則證明此對象是不可用的,說明此對象可以被GC。對於這些不可達對象,也不是一下子就被GC,而是至少要經歷兩次標記過程:如果對象在進行根搜索演算法後發現沒有與GC Roots相連接的引用鏈,那它將會第一次標記並且進行一次篩選,篩選條件是此對象有沒有必要執行finalize()方法,當對象沒有覆蓋finalize()方法或者finalize()方法已經被虛擬機調用執行過一次,這兩種情況都被視為沒有必要執行finalize()方法,對於沒有必要執行finalize()方法的將會被GC,對於有必要有必要執行的,對象在finalize()方法中可能會自救,也就是重新與引用鏈上的任何一個對象建立關聯即可。
3、如何回收
選擇不同的垃圾收集器,所使用的收集演算法也不同。
在新生代中,每次垃圾收集都發現有大批對象死去,只有少量存活,則使用復制演算法,新生代內存被分為一個較大的Eden區和兩個較小的Survivor區,每次只使用Eden區和一個Survivor區,當回收時將Eden區和Survivor還存活著的對象一次性的拷貝到另一個Survivor區上,最後清理掉Eden區和剛才使用過的Survivor區,Eden和Survivor的默認比例是8:1,可以使用-XX:SurvivorRatio來設置該比例。
而老年代中對象存活率高,沒有額外的空間對它進行分配擔保,必須使用「標記-清理」或「標記-整理」演算法。
❸ 如何理解Java虛擬機棧
堆是堆(heap),棧是棧(stack),堆棧是棧。
棧中分配的是基本類型和自定義對象的引用。
堆中分配的是對象,也就是new出來的東西。 被所有線程共享。
方法區/靜態區 存放的是類信息和static變數、常量。 被所有線程共享。
也可以這么理解:堆是用來存放對象的,棧是用來運行程序的。
堆:java的垃圾回收器會自動的回收這些不用的數據。缺點是由於要動態的分配內存,存儲效率會比較的慢。
棧:棧的優勢是存取效率比較快,僅次於寄存器,棧數據可以共享。但缺點是棧中的數據大小和生存期的固定的,缺乏靈活性。
一般每個方法的調用都會獨立有一個棧來保存對象的引用變數,在方法返回後,棧會清空,當在一段代碼塊定義一個變數時,Java就在棧中為這個變數分配內存空間,當超過變數的作用域後,Java會自動釋放掉為該變數所分配的內存空間,該內存空間可以立即被另作他用。
以32位地址操作系統為例,一個進程可擁有的虛擬內存地址范圍為0-2^32。分為兩部分,一部分留給kernel使用(kernel virtual memory),剩下的是進程本身使用, 即圖中的process virtual memory。
一個程序本質上都是由bss段、data段、text段三個組成的
bss段(Block Started by Symbol segment)通常是指用來存放程序中未初始化的全局變數的一塊內存區域,一般在初始化時bss 段部分將會清零。bss段屬於靜態內存分配,即程序一開始就將其清零了。
在C語言之類的程序編譯完成之後,已初始化的全局變數保存在.data 段中,未初始化的全局變數保存在.bss 段中。
text和data段都在可執行文件中(在嵌入式系統里一般是固化在鏡像文件中),由系統從可執行文件中載入;而bss段不在可執行文件中,由系統初始化。
❹ 如何理解JAVA堆棧
Java的堆是一個運行時數據區,類的(對象從中分配空間。這些對象通過new、newarray、anewarray和multianewarray等 指令建立,它們不需要程序代碼來顯式的釋放。堆是由垃圾回收來負責的,堆的優勢是可以動態地分配內存大小,生存期也不必事先告訴編譯器,因為它是在運行時 動態分配內存的,Java的垃圾收集器會自動收走這些不再使用的數據。但缺點是,由於要在運行時動態分配內存,存取速度較慢。
棧的優勢是,存取速度比堆要快,僅次於寄存器,棧數據可以共享。但缺點是,存在棧中的數據大小與生存期必須是確定的,缺乏靈活性。棧中主要存放一些基本類 型的變數(,int, short, long, byte, float, double, boolean, char)和對象句柄。
棧有一個很重要的特殊性,就是存在棧中的數據可以共享。假設我們同時定義:
int a = 3;
int b = 3;
編譯器先處理int a = 3;首先它會在棧中創建一個變數為a的引用,然後查找棧中是否有3這個值,如果沒找到,就將3存放進來,然後將a指向3。接著處理int b = 3;在創建完b的引用變數後,因為在棧中已經有3這個值,便將b直接指向3。這樣,就出現了a與b同時均指向3的情況。
這時,如果再令a=4;那麼編譯器會重新搜索棧中是否有4值,如果沒有,則將4存放進來,並令a指向4;如果已經有了,則直接將a指向這個地址。因此a值的改變不會影響到b的值。
要注意這種數據的共享與兩個對象的引用同時指向一個對象的這種共享是不同的,因為這種情況a的修改並不會影響到b, 它是由編譯器完成的,它有利於節省空間。而一個對象引用變數修改了這個對象的內部狀態,會影響到另一個對象引用變數。
String是一個特殊的包裝類數據。可以用:
String str = new String("abc");
String str = "abc";
兩種的形式來創建,第一種是用new()來新建對象的,它會在存放於堆中。每調用一次就會創建一個新的對象。
而第二種是先在棧中創建一個對String類的對象引用變數str,然後查找棧中有沒有存放"abc",如果沒有,則將"abc"存放進棧,並令str指向」abc」,如果已經有」abc」 則直接令str指向「abc」。
比較類裡面的數值是否相等時,用equals()方法;當測試兩個包裝類的引用是否指向同一個對象時,用==,下面用例子說明上面的理論。
String str1 = "abc";
String str2 = "abc";
System.out.println(str1==str2); //true
可以看出str1和str2是指向同一個對象的。
String str1 =new String ("abc");
String str2 =new String ("abc");
System.out.println(str1==str2); // false
用new的方式是生成不同的對象。每一次生成一個。
因此用第一種方式創建多個」abc」字元串,在內存中其實只存在一個對象而已. 這種寫法有利與節省內存空間. 同時它可以在一定程度上提高程序的運行速度,因為JVM會自動根據棧中數據的實際情況來決定是否有必要創建新對象。而對於String str = new String("abc");的代碼,則一概在堆中創建新對象,而不管其字元串值是否相等,是否有必要創建新對象,從而加重了程序的負擔。
另一方面, 要注意: 我們在使用諸如String str = "abc";的格式定義類時,總是想當然地認為,創建了String類的對象str。擔心陷阱!對象可能並沒有被創建!而可能只是指向一個先前已經創建的 對象。只有通過new()方法才能保證每次都創建一個新的對象。
由於String類的immutable性質,當String變數需要經常變換其值時,應該考慮使用StringBuffer類,以提高程序效率。
❺ 請簡單通俗易懂的解釋一下在Java中什麼叫堆 什麼叫棧 謝謝
堆:也叫動態內存,相當於一個內存池子,在java中創建對象的時候,就從堆裡面拿出一塊來存放對象;當GC(垃圾回收)回收對象的時候,又把對象佔用的內容還給堆。
舉個例子:堆就好比一個面團,類就好比一個饅頭印子,用印子從面團中取出一小塊面團,印成饅頭,這個饅頭就是這個饅頭印子類產生的對象了。當無限的創建饅頭的時候,這個面團總會被用光的,這個時候就不能在創建新的饅頭了。
所有GC就有存在的必要了,當對象不被持有的時候,GC就會把對象還給堆。也就是,當這個饅頭沒有被使用,這個饅頭就變成小面團,還給面團。
棧:也是一段內存,但是這段內存比較有特點,遵循一個先進後出的規則。
舉個例子:吃過罐裝的薯片吧,薯片一片一片的放到罐裡面去,想想,最先放進去的是不是放在罐的最底部。現在一片片把薯片取出來,是不是要從最頂部開始去,也就是最後放進去的,最先取出來。這個薯片放進去和取出來的這個過程,就是棧的工作原理啦(先進入的後出來,後進入的先出來)
在java中的棧:棧的原理明白了,其實只要是計算機只要是編程語言,什麼堆什麼棧都是一樣的,基本作用也一樣。java中可以認為,棧用來存放局部變數的。
public void fun(){
int i=0; //i 是一個局部變數,存放在棧裡面的
Object obj = new Objec(); //obj 是一個對象應用,同樣也是一個局部變數,存放在站裡面的,但是obj指向的對象,在存在堆中
}
❻ java堆棧是什麼意思
簡單的說:Java把內存劃分成兩種:一種是棧內存,一種是堆內存。x0dx0a x0dx0a在函數中定義的一些基本類型的變數和對象的引用變數都在函數的棧內存中分配。當在一段代碼塊定義一個變數時,Java就在棧中為這個變數分配內存空間,當超過變數的作用域後,Java會自動釋放掉為該變數所分配的內存空間,該內存空間可以立即被另作他用。x0dx0a x0dx0a堆內存用來存放由new創建的對象和數組。在堆中分配的內存,由Java虛擬機的自動垃圾回收器來管理。在堆中產生了一個數組或對象後,還可以在棧中定義一個特殊的變數,讓棧中這個變數的取值等於數組或對象在堆內存中的首地址,棧中的這個變數就成了數組或對象的引用變數。引用變數就相當於是為數組或對象起的一個名稱,以後就可以在程序中使用棧中的引用變數來訪問堆中的數組或對象