堆內存用於存儲new創建的對象和數組,堆中分配的內存由Java虛擬機的自動垃圾收集器管理。在堆中生成數組或對象後,還可以在堆棧中定義壹個特殊變量,使堆棧中該變量的值等於堆內存中數組或對象的第壹個地址,堆棧中該變量成為數組或對象的引用變量。稍後,您可以使用堆棧中的引用變量來訪問堆中的數組或對象,引用變量相當於數組或對象的名稱。引用變量是普通變量,定義時在堆棧中分配,程序運行超出其作用域後釋放。數組和對象本身是在堆中分配的。即使程序運行在使用new生成數組或對象的語句所在的代碼塊之外,數組和對象本身占用的內存也不會被釋放。當沒有引用變量指向數組和對象時,數組和對象就變成了垃圾,它們不能再使用了,但它們仍然占用內存空間,並在不確定的時間被垃圾收集器拿走(釋放)。
這就是Java占用更多內存的原因。實際上,堆棧中的變量指向堆內存中的變量,也就是Java中的指針!
java中的內存分配策略及堆和棧的比較
1內存分配策略
根據編譯原理,程序運行時內存分配有三種策略,即靜態、堆棧和堆積。
靜態存儲分配意味著每個數據對象在運行時的存儲空間需求可以在編譯時確定,因此可以在編譯時為它們分配固定的內存空間。這種分配策略要求程序代碼中不允許使用可變數據結構(如可變數組),也不允許使用嵌套或遞歸結構,因為它們會導致編譯器無法計算出準確的存儲空間需求。
堆棧式存儲分配也可以稱為動態存儲分配,它是通過類似於堆棧的運行堆棧來實現的。與靜態存儲分配相反,在堆棧存儲方案中,程序的數據區需求在編譯時是完全未知的,只能在運行時知道,但規定當進入運行中的程序模塊時,必須知道程序模塊所需的數據區大小才能為其分配內存。與我們在《數據結構》中熟悉的堆棧壹樣,堆棧式存儲分配也是基於高級和向後的。
靜態存儲分配需要在編譯時知道所有變量的存儲要求,而堆棧存儲分配需要在進程入口處知道所有存儲要求,而堆存儲分配則專門負責那些在編譯時或運行時模塊入口處無法確定存儲要求的數據結構的內存分配,例如變長字符串和對象實例。堆由大型可用塊或空閑塊組成,堆中的內存可以按任意順序分配和釋放。
2堆和棧比較
上述定義是從編譯原理教材中總結出來的。除了靜態存儲分配之外,它非常枯燥且難以理解。讓我們拋開靜態存儲分配,專註於比較堆和棧:
比較堆和棧的功能和作用,堆主要用於存儲對象,棧主要用於執行程序。這種差異主要是由堆和棧的特征決定的:
在編程中,如C/C++,所有方法調用都是通過堆棧進行的,所有局部變量和形參都是從堆棧中分配內存空間的。事實上,這不是分配,只是從堆棧頂部使用它。就像工廠裏的傳送帶壹樣,堆棧指針會自動指引妳到放東西的地方。妳要做的就是放下東西。退出函數時,可以通過修改堆棧指針來銷毀堆棧中的內容。這種模式是最快的,當然應該用它來運行程序。應該註意的是,例如,當為要調用的程序模塊分配數據區時,應該預先知道該數據區的大小,也就是說,盡管分配是在程序運行時進行的,但分配大小是固定不變的,並且該“大小”是在編譯時確定的,而不是在運行時確定的。
堆是壹個應用程序,它在運行時請求操作系統分配自己的內存。由於內存分配由操作系統管理,分配和銷毀都需要時間,因此使用堆的效率很低。但是堆的優點是編譯器不需要知道從堆中分配多少存儲空間,也不需要知道存儲的數據將在堆中停留多長時間,因此在使用堆保存數據時將獲得更大的靈活性。事實上,面向對象的多態和堆內存分配是必不可少的,因為多態變量所需的存儲空間只能在運行時創建對象後才能確定。在C++中,當需要創建對象時,只需使用new命令來編譯相關代碼。當執行這些代碼時,數據將自動保存在堆中。當然,為了實現這種靈活性,需要付出壹定的代價:在堆中分配存儲空間需要更長的時間!這也是我們剛才提到的效率低的原因。看來列寧同誌說得好。人的優點往往是人的缺點,人的缺點往往是人的優點。
JVM中的3個堆和棧
JVM是基於堆棧的虛擬機。JVM為每個新創建的線程分配壹個堆棧。也就是說,對於壹個Java程序來說,它的操作是通過操作堆棧來完成的。堆棧以幀為單位保存線程的狀態。JVM只對堆棧執行兩種操作:以幀為單位的堆棧推送和堆棧彈出。
我們知道線程正在執行的方法稱為該線程的當前方法。我們可能不知道當前方法使用的幀稱為當前幀。當壹個線程激活壹個Java方法時,JVM將把壹個新的幀推入線程的Java堆棧。該幀自然成為當前幀。在執行該方法期間,該框架將用於存儲參數、局部變量、中間計算過程和其他數據。這個框架類似於編譯原理中的活動記錄的概念。
從Java的這種分配機制,可以這樣理解堆棧:堆棧是操作系統或線程(操作系統中支持多線程的線程)為該線程建立的存儲區域,具有先進後出的特性。
每個Java應用程序唯壹地對應壹個JVM實例,每個實例唯壹地對應壹個堆。應用程序在運行期間創建的所有類實例或數組都放在這個堆中,並由應用程序的所有線程共享。與C/C++不同,Java中堆內存的分配是自動初始化的。Java中所有對象的存儲空間都是在堆中分配的,但是這個對象的引用是在堆棧中分配的,也就是說,當創建壹個對象時,在堆中分配的內存實際上創建了這個對象,而在堆棧中分配的內存只是指向這個堆對象的指針(引用)。
Java中的堆和棧
Java將內存分為兩種類型:壹種是堆棧內存,另壹種是堆內存。
函數中定義的壹些基本類型的變量和對象的引用變量被分配在函數的堆棧內存中。
當在代碼塊中定義變量時,Java在堆棧中為該變量分配內存空間。當超出變量的範圍時,Java會自動釋放為變量分配的內存空間,這些空間可以立即用於其他用途。
堆內存用於存儲由new創建的對象和數組。
堆中分配的內存由Java虛擬機的自動垃圾收集器管理。
在堆中生成數組或對象後,還可以在堆棧中定義壹個特殊變量,使堆棧中該變量的值等於堆內存中數組或對象的第壹個地址,堆棧中該變量成為數組或對象的引用變量。
引用變量相當於賦予數組或對象的名稱,將來可以使用堆棧中的引用變量來訪問堆中的數組或對象。
具體來說:
堆棧和堆是Java在Ram中存儲數據的地方。與C++不同,Java自動管理堆棧和堆,程序員不能直接設置它們。
Java的堆是壹個運行時數據區,類的對象從中分配空間。這些對象由諸如new、anewarray、anewararray和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並指向4。如果已經存在,直接將A指向此地址。因此,A值的變化不會影響B的值。需要註意的是,這種數據的* * *享受與兩個對象同時引用壹個對象的* * *享受不同,因為在這種情況下,A的修改不會影響B,它由編譯器完成,這有利於節省空間。但是,對象引用變量會修改該對象的內部狀態,這將影響另壹個對象引用變量。