詳解JAVA 記憶體管理
前一段時間粗略看了一下《深入Java虛擬機器 第二版》,可能是因為工作才一年的原因吧,看著十分的吃力。畢竟如果具體到細節的話,Java虛擬機器涉及的內容太多了。可能再過一兩年去看會合適一些吧。
不過看了一遍《深入Java虛擬機器》再來理解Java記憶體管理會好很多。接下來一起學習下Java記憶體管理吧。
請注意上圖的這個:
我們再來複習下程序與執行緒吧:
程序是具有一定獨立功能的程式關於某個資料集合上的一次執行活動,程序是系統進行資源分配和排程的一個獨立單位。
執行緒是程序的一個實體,是CPU排程和分派的基本單位,它是比程序更小的能獨立執行的基本單位。執行緒自己基本上不擁有系統資源,只擁有一點在執行中必不可少的資源(如程式計數器,一組暫存器和棧),但是它可與同屬一個程序的其他的執行緒共享程序所擁有的全部資源。
似乎現在更好理解了一些:
方法區和堆是分配給程序的,也就是所有執行緒共享的。
而棧和程式計數器,則是分配給每個獨立執行緒的,是執行過程中必不可少的資源。
下面我們逐個看下棧、堆、方法區和程式計數器。
1、方法區(Method Area)
方法區(Method Area)與Java堆一樣,是各個執行緒共享的記憶體區域,它用於儲存已被虛擬機器載入的類資訊、常量、靜態變數、即時編譯器編譯後的程式碼等資料。雖然Java虛擬機器規範把方法區描述為堆的一個邏輯部分,但是它卻有一個別名叫做Non-Heap(非堆),目的應該是與Java堆區分開來。
2、程式計數器(Program Counter Register)
程式計數器(Program Counter Register)是一塊較小的記憶體空間,它的作用可以看做是當前執行緒所執行的位元組碼的行號指示器。在虛擬機器的概念模型裡(僅是概念模型,各種虛擬機器可能會通過一些更高效的方式去實現),位元組碼直譯器工作時就是通過改變這個計數器的值來選取下一條需要執行的位元組碼指令,分支、迴圈、跳轉、異常處理、執行緒恢復等基礎功能都需要依賴這個計數器來完成。
下面重點解下Java記憶體管理中的棧和堆。
3、棧(Stacks)
在Java中,JVM中的棧記錄了執行緒的方法呼叫。每個執行緒擁有一個棧。在某個執行緒的執行過程中,如果有新的方法呼叫,那麼該執行緒對應的棧就會增加一個儲存單元,即幀(frame)。在frame中,儲存有該方法呼叫的引數、區域性變數和返回地址。
Java的引數和區域性變數只能是基本型別的變數(比如int),或者物件的引用(reference)。因此,在棧中,只儲存有基本型別的變數和物件引用。引用所指向的物件儲存在堆中。(引用可能為Null值,即不指向任何物件)。
當被呼叫方法執行結束時,該方法對應的幀將被刪除,引數和區域性變數所佔據的空間也隨之釋放。執行緒回到原方法,繼續執行。當所有的棧都清空時,程式也隨之執行結束。
本地方法棧與虛擬機器棧的區別:
本地方法棧(Native Method Stacks)與虛擬機器棧所發揮的作用是非常相似的,其區別不過是虛擬機器棧為虛擬機器執行Java方法(也就是位元組碼)服務,而本地方法棧則是為虛擬機器使用到的Native方法服務。虛擬機器規範中對本地方法棧中的方法使用的語言、使用方式與資料結構並沒有強制規定,因此具體的虛擬機器可以自由實現它。甚至有的虛擬機器(譬如Sun HotSpot虛擬機器)直接就把本地方法棧和虛擬機器棧合二為一。與虛擬機器棧一樣,本地方法棧區域也會丟擲StackOverflowError和OutOfMemoryError異常。
4、堆(Heap)
堆是JVM中一塊可自由分配給物件的區域。當我們談論垃圾回收(garbage collection)時,我們主要回收堆(heap)的空間。
Java的普通物件存活在堆中。與棧不同,堆的空間不會隨著方法呼叫結束而清空。因此,在某個方法中建立的物件,可以在方法呼叫結束之後,繼續存在於堆中。這帶來的一個問題是,如果我們不斷的建立新的物件,記憶體空間將最終消耗殆盡。
垃圾回收(Garbage Collection,GC)
垃圾回收(garbage collection,簡稱GC)可以自動清空堆中不再使用的物件。垃圾回收機制最早出現於1959年,被用於解決Lisp語言中的問題。垃圾回收是Java的一大特徵。並不是所有的語言都有垃圾回收功能。比如在C/C++中,並沒有垃圾回收的機制。程式設計師需要手動釋放堆中的記憶體。
由於不需要手動釋放記憶體,程式設計師在程式設計中也可以減少犯錯的機會。利用垃圾回收,程式設計師可以避免一些指標和記憶體洩露相關的bug(這一類bug通常很隱蔽)。但另一方面,垃圾回收需要耗費更多的計算時間。垃圾回收實際上是將原本屬於程式設計師的責任轉移給計算機。使用垃圾回收的程式需要更長的執行時間。
在Java中,物件的是通過引用使用的(把物件相像成致命的毒物,引用就像是用於提取毒物的鑷子)。如果不再有引用指向物件,那麼我們就再也無從呼叫或者處理該物件。這樣的物件將不可到達(unreachable)。垃圾回收用於釋放不可到達物件所佔據的記憶體。這是垃圾回收的基本原則。
早期的垃圾回收採用引用計數(reference counting)的機制。每個物件包含一個計數器。當有新的指向該物件的引用時,計數器加1。當引用移除時,計數器減1。當計數器為0時,認為該物件可以進行垃圾回收。
然而,一個可能的問題是,如果有兩個物件迴圈引用(cyclic reference),比如兩個物件互相引用,而且此時沒有其它(指向A或者指向B)的引用,我們實際上根本無法通過引用到達這兩個物件。
因此,我們以棧和static資料為根(root),從根出發,跟隨所有的引用,就可以找到所有的可到達物件。也就是說,一個可到達物件,一定被根引用,或者被其他可到達物件引用。
以上就是詳解JAVA 記憶體管理的詳細內容,更多關於JAVA 記憶體管理的資料請關注我們其它相關文章!