1. 程式人生 > >Java 內存回收機制——GC機制

Java 內存回收機制——GC機制

出棧 font 學習 eap 實現 機制 virt 鏈接 http

一、Java GC 概念說明

  Java GC(Garbage Collection,垃圾收集,垃圾回收)機制,是Java與C++/C的主要區別之一,作為Java開發者,一般不需要專門編寫內存回收和垃圾清理代碼,對內存泄露和溢出的問題,也不需要像C程序員那樣戰戰兢兢。這是因為在Java虛擬機中,存在自動內存管理和垃圾清掃機制。概括地說,該機制對JVM(Java Virtual Machine)中的內存進行標記,並確定哪些內存需要回收,根據一定的回收策略,自動的回收內存,永不停息(Nerver Stop)的保證JVM中的內存空間,防止出現內存泄露和溢出問題。

  Java GC機制主要完成3件事:確定哪些內存需要回收,確定什麽時候需要執行GC,如何執行GC。下面我們將從4個方面學習Java GC機制,1,內存是如何分配的;2,如何保證內存不被錯誤回收(即:哪些內存需要回收);3,在什麽情況下執行GC以及執行GC的方式;4,如何監控和優化GC機制。

二、Java內存區域劃分

  了解Java GC機制,必須先清楚在JVM中內存區域的劃分。在Java運行時的數據區裏,由JVM管理的內存區域分為下圖幾個模塊:

技術分享圖片

1. 程序計數器(Program Counter Register)

程序計數器是一個比較小的內存區域,用於指示當前線程所執行的字節碼執行到了第幾行,可以理解為是當前線程的行號指示器。字節碼解釋器在工作時,會通過改變這個計數器的值來取下一條語句指令。

每個程序計數器只用來記錄一個線程的行號,所以它是線程私有(一個線程就有一個程序計數器)的。

如果程序執行的是一個Java方法,則計數器記錄的是正在執行的虛擬機字節碼指令地址;如果正在執行的是一個本地(native,由C語言編寫完成)方法,則計數器的值為Undefined,由於程序計數器只是記錄當前指令地址,所以不存在內存溢出的情況,因此,程序計數器也是所有JVM內存區域中唯一一個沒有定義OutOfMemoryError的區域。

2. Java虛擬機棧(Java Virtual Machine Stacks)

一個線程的每個方法在執行的同時,都會創建一個棧幀(Statck Frame),棧幀中存儲的有局部變量表、操作站、動態鏈接、方法出口等,當方法被調用時,棧幀在JVM棧中入棧,當方法執行完成時,棧幀出棧。

局部變量表中存儲著方法的相關局部變量,包括各種基本數據類型,對象的引用,返回地址等。在局部變量表中,只有long和double類型會占用2個局部變量空間(Slot,對於32位機器,一個Slot就是32個bit),其它都是1個Slot。需要註意的是,局部變量表是在編譯時就已經確定好的,方法運行所需要分配的空間在棧幀中是完全確定的,在方法的生命周期內都不會改變。

虛擬機棧中定義了兩種異常,如果線程調用的棧深度大於虛擬機允許的最大深度,則拋出StatckOverFlowError(棧溢出);不過多數Java虛擬機都允許動態擴展虛擬機棧的大小(有少部分是固定長度的),所以線程可以一直申請棧,直到內存不足,此時,會拋出OutOfMemoryError(內存溢出)。

每個線程對應著一個虛擬機棧,因此虛擬機棧也是線程私有的。

3. 本地方法棧(Native Method Stacks)

本地方法棧在作用,運行機制,異常類型等方面都與虛擬機棧相同,唯一的區別是:虛擬機棧是執行Java方法的,而本地方法棧是用來執行native方法的,在很多虛擬機中(如Sun的JDK默認的HotSpot虛擬機),會將本地方法棧與虛擬機棧放在一起使用。

本地方法棧也是線程私有的。

4. 堆區(Heap)

堆區是理解Java GC機制最重要的區域,沒有之一。在JVM所管理的內存中,堆區是最大的一塊,堆區也是Java GC機制所管理的主要內存區域,堆區由所有線程共享,在虛擬機啟動時創建。堆區的存在是為了存儲對象實例,原則上講,所有的對象都在堆區上分配內存(不過現代技術裏,也不是這麽絕對的,也有棧上直接分配的)。

一般的,根據Java虛擬機規範規定,堆內存需要在邏輯上是連續的(在物理上不需要),在實現時,可以是固定大小的,也可以是可擴展的,目前主流的虛擬機都是可擴展的。如果在執行垃圾回收之後,仍沒有足夠的內存分配,也不能再擴展,將會拋出OutOfMemoryError:Java heap space異常。

5. 方法區(Method Area)

方法區是各個線程共享的區域,用於存儲已經被虛擬機加載的類信息(即加載類時需要加載的信息,包括版本、field、方法、接口等信息)、final常量、靜態變量、編譯器即時編譯的代碼等。

方法區在物理上也不需要是連續的,可以選擇固定大小或可擴展大小,並且方法區比堆還多了一個限制:可以選擇是否執行垃圾收集。一般的,方法區上執行的垃圾收集是很少的。但這也不代表著在方法區上完全沒有垃圾收集,其上的垃圾收集主要是針對常量池的內存回收和對已加載類的卸載。

在方法區上進行垃圾收集,條件苛刻而且相當困難,效果也不令人滿意,所以一般不做太多考慮,可以留作以後進一步深入研究時使用。

在方法區上定義了OutOfMemoryError:PermGen space異常,在內存不足時拋出。

三、Java對象的訪問方式

一個Java的引用訪問涉及到3個內存區域:JVM棧,堆,方法區。

以最簡單的本地變量引用:Object obj = new Object()為例:

  • Object obj表示一個本地引用,存儲在JVM棧的本地變量表中,表示一個reference類型數據;
  • new Object()作為實例對象數據存儲在堆中;
  • 堆中還記錄了Object類的類型信息(接口、方法、field、對象類型等)的地址,這些地址所執行的數據存儲在方法區中;

在Java虛擬機規範中,對於通過reference類型引用訪問具體對象的方式並未做規定,目前主流的實現方式主要有兩種:

1. 通過句柄訪問:

技術分享圖片

通過句柄訪問的實現方式中,JVM堆中會專門有一塊區域用來作為句柄池,存儲相關句柄所執行的實例數據地址(包括在堆中地址和在方法區中的地址)。這種實現方法由於用句柄表示地址,因此十分穩定。

2. 通過直接指針訪問:

技術分享圖片

通過直接指針訪問的方式中,reference中存儲的就是對象在堆中的實際地址,在堆中存儲的對象信息中包含了在方法區中的相應類型數據。這種方法最大的優勢是速度快,在HotSpot虛擬機中用的就是這種方式。

新生代、老年代、永久代:這三個類型的GC形式後續再進行總結,目前就寫到這裏 // TODO

目前需要仔細研究一下:https://www.zhihu.com/question/53613423

四、參考資料

《JAVA編程思想》第5章; 《Java深度歷險》Java垃圾回收機制與引用類型; 《深入理解Java虛擬機:JVM高級特效與最佳實現》,第2-3章; 成為JavaGC專家Part II — 如何監控Java垃圾回收機制, http://www.importnew.com/2057.html JDK5.0垃圾收集優化之--Don‘t Pause,http://calvin.iteye.com/blog/91905 【原】java內存區域理解-初步了解,http://iamzhongyong.iteye.com/blog/1333100 關於施用full gc頻繁的分析及解決:http://www.07net01.com/zhishi/383213.html

Java 內存回收機制——GC機制