Java內存分配中,堆和棧的區別

參考:https://www.jianshu.com/p/65b9f5f79716
根據編譯原理,程序在運行時的內存分配策略有三種:

靜態 Static

指在編譯時就能確定的每個數據目標在運行時刻需要的存儲空間需求。因而在編譯的時候就可以給他們分配固定的存儲空間。 這種數據目標在編譯時就爲他們分配固定的內存。
限制:
代碼中不能有可變數據結構,如數組。
代碼中不允許有遞歸或嵌套結構的出現。

public class EaseConstant {
    public static final String MESSAGE_ATTR_IS_VOICE_CALL = "is_voice_call";
    public static final String MESSAGE_ATTR_IS_VIDEO_CALL = "is_video_call";
   
    public static final String MESSAGE_ATTR_IS_BIG_EXPRESSION = "em_is_big_expression";
    public static final String MESSAGE_ATTR_EXPRESSION_ID = "em_expression_id";
   
    public static final String MESSAGE_ATTR_AT_MSG = "em_at_list";
    public static final String MESSAGE_ATTR_VALUE_AT_MSG_ALL = "ALL";

    public static final int CHATTYPE_SINGLE = 1;
    public static final int CHATTYPE_GROUP = 2;
    public static final int CHATTYPE_CHATROOM = 3;
    
    public static final String EXTRA_CHAT_TYPE = "chatType";
    public static final String EXTRA_USER_ID = "userId";
}

棧式 Stack

棧式存儲分配可稱爲動態存儲分配,是由一個類似於堆棧的運行棧來實現的,和靜態存儲分配相反,在棧式存儲方案中,程序對數據區的需求在編譯時是完全未知的,只有到運行的時候才能知道。

指在編譯時不能確定大小,但在運行的時候能夠確定,且規定在運行中進入一個程序模塊時,就必須知道該模塊所需要的數據區大小,才能爲其分配內存,和我們在數據結構中所知道的棧一樣,內存分配爲e棧原則,先進後出的原則進行分配。

分配是在運行時執行的,但是大小是在編譯時確定的;

特點:
在C/C++中,所有的方法調用都是通過棧來進行的,所有局部變量,形式參數都是從棧中分配內存空間的。

棧的分配和回收:
棧分配內存空間:從棧低向棧頂,依次存儲;
棧回收內存空間:修改棧頂指針的位置,完成棧中內容銷燬,這樣的模式速度很快。

棧 :存放基本數據類型,速度快

  • 棧中主要存放一些基本類型的變量(int, short, long, byte, float, double, boolean,
    char)和對象句柄;
  • 棧的存取速度比堆要快;
  • 棧數據可以共享;
  • 棧的數據大小與生存期必須是確定的,缺乏靈活性。

堆式 Heap

指編譯時,運行時模塊入口都不能確定存儲要求的數據結構的內存分配。
比如可變長度的串和對象實例。
堆由大片的可利用的塊或空閒組成,堆中的內存可以按照任意順序分配和釋放。

堆是在運行的時候,請求操作系統分配給自己內存,由於從操作系統管理的內存分配,所以在分配和銷燬的時候都要佔用時間,因此對的效率低下。

堆的優點:編譯時不必知道要從堆裏分配多少存儲空間,也不必知道存儲的數據要在堆裏停留多長時間,因此堆存儲數據時,靈活性比較大;

在面向對象編程中,堆是必不可少的,因爲面向對象的多態性,多態變量所需的存儲空間只有在運行時創建了對象之後才能確定。

堆: 用new建立,垃圾自動回收負責回收

  • 堆是一個"運行時"數據區,類實例化的對象就是從堆上去分配空間的;
  • 在堆上分配空間是通過"new"等指令建立的;
  • Java針對堆的操作和C++的區別就是,Java不需要在空間不用的時候來顯式的釋放;
  • Java的堆是由Java的垃圾回收機制來負責處理的,堆是動態分配內存大小,垃圾收集器可以自動回收不再使用的內存空間。
  • 但缺點是,因爲在運行時動態分配內存,所以內存的存取速度較慢。

========================================================

堆和棧的比較
在這裏插入圖片描述
JVM中的堆棧
JVM是基於堆棧的虛擬機,JVM中的堆棧是兩塊不同的存儲區域。
JVM爲每個線程程都分配了一個堆和棧,所以對於java程序來說,程序的運行是通過對堆棧的操作來完成的。
在這裏插入圖片描述
一個對象的大小是不可估計的,或者說是可以動態變化的,但是在JVM棧中,一個對象只對應了一個4btye的引用(JVM堆JVM棧分離的好處:))。

爲什麼不把基本類型放JVM堆中呢?
因爲基本類型其佔用的空間一般是1~8個字節(需要空間比較少),而且因爲是基本類型,所以不會出現動態增長的情況(長度固定),因此JVM棧中存儲就夠了,如果把他存在JVM堆中是沒有什麼意義的(還會浪費空間,後面說明)。可以這麼說,基本類型和對象的引用都是存放在JVM棧中,而且都是幾個字節的一個數,因此在程序運行時,他們的處理方式是統一的。

但是基本類型、對象引用和對象本身就有所區別了,因爲一個是JVM棧中的數據一個是JVM堆中的數據的引用。最常見的一個問題就是,Java中參數傳遞時的問題。

JAVA中堆棧的應用
Java中的參數傳遞時傳值呢?還是傳引用?

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

總結:傳遞的是對象的引用值或是基本類型的值;

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

但是傳引用的錯覺是如何造成的呢?
在運行JVM棧中,基本類型和引用的處理是一樣的,都是傳值,所以,如果是傳引用的方法調用,也同時可以理解爲“傳引用值”的傳值調用,即引用的處理跟基本類型是完全一樣的。但是當進入被調用方法時,被傳遞的這個引用的值,被程序解釋(或者查找)到JVM堆中的對象,這個時候纔對應到真正的對象。如果此時進行修改,修改的是引用對應的對象,而不是引用本身,即:修改的是JVM堆中的數據。所以這個修改是可以保持的了。

對象,從某種意義上說,是由基本類型組成的。可以把一個對象看作爲一棵樹,對象的屬性如果還是對象,則還是一顆樹(即非葉子節點),基本類型則爲樹的葉子節點。程序參數傳遞時,被傳遞的值本身都是不能進行修改的,但是,如果這個值是一個非葉子節點(即一個對象引用),則可以修改這個節點下面的所有內容。

JVM堆和JVM棧中,JVM棧是程序運行最根本的東西。程序運行可以沒有JVM堆,但是不能沒有JVM棧。而JVM堆是爲JVM棧進行數據存儲服務,說白了JVM堆就是一塊共享的內存。不過,正是因爲JVM堆和JVM棧的分離的思想,才使得Java的垃圾回收成爲可能。

JVM中線程堆棧
線程(thread),有時被稱爲輕量級進程(Lightweight Process,LWP),是程序執行流的最小單元。一個標準的線程由線程ID,當前指令指針(PC),寄存器集合和堆棧組成。另外,線程是進程中的一個實體,是被系統獨立調度和分派的基本單位,線程自己不擁有系統資源,只擁有一點在運行中必不可少的資源,但它可與同屬一個進程的其它線程共享進程所擁有的全部資源。一個線程可以創建和撤消另一個線程,同一進程中的多個線程之間可以併發執行。由於線程之間的相互制約,致使線程在運行中呈現出間斷性。線程也有就緒、阻塞和運行三種基本狀態。  線程是程序中一個單一的順序控制流程.在單個程序中同時運行多個線程完成不同的工作,稱爲多線程。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章