前言
在segmentfault上看到一個問題:java有完善的GC機制,那麼在java中是否會出現記憶體洩漏的問題,以及能否給出一個記憶體洩漏的案例。本問題視圖給出此問題的完整答案。
垃圾回收機制簡介
在程式運作過程中,每建立一個物件都會被分配一定的記憶體以儲存物件資料。如果只是不停的分配內存,那麼程式遲早會面臨內存不足的問題。所以在任何語言中,都會有一個記憶體回收機制來釋放過期物件的內存,以確保記憶體能夠重複使用。
記憶體回收機制依照實現角色的不同可以分為兩種,一種是程式設計師手動實現記憶體的釋放(例如C語言)另一種則是語言內建的記憶體回收機制例如本文將要介紹的java垃圾回收機制。
Java的垃圾回收機制
在程式的運行時環境中,java虛擬機提供了一個系統級的垃圾回收(GC,Carbage Collection)線程,它負責回收失去引用的物件佔用的記憶體。理解GC的前提是理解一些和垃圾回收相關的概念,下文一一介紹這些概念。
物件在jvm堆區的狀態
Java物件的實例儲存在jvm的堆疊區,對於GC執行緒來說,這些物件有三種狀態。
1. 可觸及狀態:程式中還有變數引用,那麼此物件為可觸及狀態。
2. 可復活狀態:當程式中已經沒有變數引用這個對象,那麼此對象由可觸及狀態轉為可復活狀態。 CG執行緒將在一定的時間準備呼叫此物件的finalize方法(finalize方法繼承或重寫子Object),finalize方法內的程式碼有可能將物件轉為可觸及狀態,否則物件轉換為不可觸及狀態。
3. 不可觸及狀態:只有當物件處於不可觸及狀態時,GC執行緒才能回收此物件的記憶體。
GC為了能夠正確釋放對象,必須監控每個對象的運作狀態,包括對象的申請、引用、被引用、賦值等,GC都需要監控,所以無論一個對象處於上文的任何狀態GC都會知道。
上文說到,GC執行緒會在一定的時間執行可復活狀態物件的finalize方法,那麼何時執行呢?由於不同的JVM實作者可能會使用不同的演算法來管理GC,所以在任何時候,開發者無法預料GC執行緒進行各項操作(包括偵測物件狀態、釋放物件記憶體、呼叫物件的finalize方法)的時機。雖然可以透過System.gc()和Runtime.gc()函數提醒GC執行緒盡快進行垃圾回收操作,但這也無法保證GC執行緒馬上就會進行對應的回收操作。
記憶體洩漏
記憶體洩漏指由於錯誤的設計造成程式未能釋放已經不再使用的內存,造成資源浪費。 GC會自動清理失去引用的物件所佔用的記憶體。但是,由於程式設計錯誤而導致某些物件始終被引用,那麼將會出現記憶體洩漏。
例如下面的例子。使用陣列實作了一個棧,有入棧和出棧兩個操作。
import com.sun.javafx.collections.ElementObservableListDecorator; import com.sun.swing.internal.plaf.metal.resources.metal_sv; import java.beans.ExceptionListener; import java.util.EmptyStackException; /** * Created by peng on 14-9-21. */ public class MyStack { private Object[] elements; private int Increment = 10; private int size = 0; public MyStack(int size) { elements = new Object[size]; } //入栈 public void push(Object o) { capacity(); elements[size++] = o; } //出栈 public Object pop() { if (size == 0) throw new EmptyStackException(); return elements[--size]; } //增加栈的容量 private void capacity() { if (elements.length != size) return; Object[] newArray = new Object[elements.length + Increment]; System.arraycopy(elements, 0, newArray, 0, size); } public static void main(String[] args) { MyStack stack = new MyStack(100); for (int i = 0; i < 100; i++) stack.push(new Integer(i)); for (int i = 0; i < 100; i++) { System.out.println(stack.pop().toString()); } } }
這個程式是可用的,支援常用的入棧和出棧操作。但是,有一個問題沒有處理好,就是當出棧操作的時候,並沒有釋放數組中出棧元素的引用,這導致程式將一直保持對這個Object的引用(此object由數組引用),GC永遠認為此物件是可觸及的,也就更加談不上釋放其記憶體了。這就是記憶體洩漏的一個典型案例。針對此,修改後的程式碼為:
//出栈 public Object pop() { if (size == 0) throw new EmptyStackException(); Object o = elements[--size]; elements[size] = null; return o; }
以上這篇深入理解Java垃圾回收機制以及內存洩漏就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支持PHP中文網。
更多深入理解Java垃圾回收機制以及內存洩漏相關文章請關注PHP中文網!