java內存泄漏與內存溢出

一、爲什麼要了解內存泄漏和內存溢出


1、內存泄露一般是代碼設計存在缺陷導致的,通過了解內存泄露的場景,可以避免不必要的內存溢出和提高自己的代碼編寫水平。
2、通過了解內存溢出的幾種常見情況,可以在出現內存溢出的時候快速的定位問題的位置,縮短解決故障的時間。


二 、基本概念


(1) 軟件內存分配的時候一般會放在三種位置:靜態存儲區域、堆和棧


1、靜態存儲區:內存在程序編譯的時候就已經分配好,這塊內存在程序整個運行期間都存在。它主要存放靜態數據、全局static數據和常量
2、棧:就是CPU的寄存器(並不是內存),特點是容量很小但是速度最快,函數或者方法體內聲明的變量或者指向對象的引用、局部變量即分配在這裏,生命週期到該函數或者方法體尾部即止
3、堆:就是動態內存分配區(就是實體的內存RAM),java中的new和垃圾回收直接操作的就是這裏的區域,類的成員變量分配在這裏

從上面即可看出靜態存儲區域是編譯時已經分配好的,棧是CPU自動控制的,那麼我們所討論的內存泄漏問題實際上就是分配在堆裏面的內存出現了問題。


(2) 內存泄漏:指程序中動態分配內存給一些臨時對象,但是對象不會被GC所回收,它始終佔用內存。即被分配的對象可達但已無用。


(3) 內存溢出:指程序運行過程中無法申請到足夠的內存而導致的一種錯誤。


(4) 內存泄漏和內存溢出的區別:

內存泄漏是導致內存溢出的原因之一;內存泄露積累起來將導致內存溢出。內存泄漏可以通過完善代碼來避免;內存溢出可以通過調整配置來減少發生頻率,但無法徹底避免。


三 、內存泄漏的原因


引起內存泄漏的根本原因:長生命週期的對象持有短生命週期對象的引用,儘管短生命週期對象已經不再需要,但是因爲長生命週期持有它的引用而導致其不能被回收,從而引起內存泄漏。


四 、內存泄漏常見類型


(1)靜態集合類引起內存泄漏

class StaticTest{   
    private static Vector v = new Vector(10);
    public void init(){
        for (int i = 1; i < 100; i++){
            Object object = new Object();
            v.add(object);
            object = null;
        }
    }
}

說明:在這個例子中,我們循環申請Object對象,並將所申請的對象放入一個Vector中,如果我們僅僅釋放引用本身,那麼Vector仍然引用該對象,所以這個對象對GC來說是不可回收的,最簡單的方法就是將Vector對象設置爲null。


(2)各種連接

比如數據庫連接(dataSourse.getConnection()),網絡連接(socket)和io連接,除非其顯式的調用了其close()方法將其連接關閉,否則是不會自動被GC 回收的。對於Resultset 和Statement 對象可以不進行顯式回收,但Connection 一定要顯式回收,因爲Connection 在任何時候都無法自動回收,而Connection一旦回收,Resultset 和Statement 對象就會立即爲NULL。但是如果使用連接池,情況就不一樣了,除了要顯式地關閉連接,還必須顯式地關閉Resultset Statement 對象(關閉其中一個,另外一個也會關閉),否則就會造成大量的Statement 對象無法釋放,從而引起內存泄漏。這種情況下一般都會在try裏面去的連接,在finally裏面釋放連接。


(3)監聽器

在Java中,我們經常會使用到監聽器,如對某個控件添加單擊監聽器addOnClickListener(),但往往釋放對象的時候會忘記刪除監聽器,這就有可能造成內存泄漏。好的方法就是,在釋放對象的時候,應該記住釋放所有監聽器,這就能避免了因爲監聽器而導致的內存泄漏。

(4)內部類和外部模塊的引用

內部類的引用是比較容易遺忘的一種,而且一旦沒釋放可能導致一系列的後繼類對象沒有釋放。此外程序員還要小心外部模塊不經意的引用,例如程序員A 負責A 模塊,調用了B 模塊的一個方法如:
public void registerMsg(Object b);
這種調用就要非常小心了,傳入了一個對象,很可能模塊B就保持了對該對象的引用,這時候就需要注意模塊B 是否提供相應的操作去除引用。


(5)單例模式

不正確使用單例模式是引起內存泄漏的一個常見問題,單例對象在初始化後將在JVM的整個生命週期中存在(以靜態變量的方式),如果單例對象持有外部的引用,那麼這個對象將不能被JVM正常回收,導致內存泄漏

class A{
public A(){
B.getInstance().setA(this);
}}
//B類採用單例模式
class B{
private A a;
private static B instance=new B();
public B(){}
public static B getInstance(){
return instance;
}
public void setA(A a){
this.a=a;
}}

說明:顯然B採用單例模式,它持有一個A對象的引用,而這個A類的對象將不能被回收。想象下如果A是個比較複雜的對象或者集合類型會發生什麼情況


五 、如何避免內存泄漏


1、儘早釋放無用對象的引用

2、使用字符串處理時避免使用String,應使用StringBuffer,每一個String對象都得獨立佔用內存一塊區域

3、儘量少用靜態變量,因爲靜態變量存放在永久代(方法區),永久代基本不參與垃圾回收

4、避免在循環中創建對象


六 、內存溢出常見類型及解決方案


1、java.lang.OutOfMemoryError: PermGen space

JVM管理兩種類型的內存,堆和非堆。堆是給開發人員用的,是在JVM啓動時創建;非堆是留給JVM自己用的,用來存放類的信息的。非堆運行期內GC不會釋放空間。如果webapp用了大量的第三方jar或者應用有太多的class文件而恰好MaxPermSize設置較小,超出了也會導致這塊內存的佔用過多造成溢出。

解決方案:
可在配置文件中設置MaxPermSize:

<jvm-arg>-XX:PermSize=128m</jvm-arg><!-- 非堆內存初始值-->
<jvm-arg>-XX:MaxPermSize=256m</jvm-arg><!-- 最大非堆內存的大小-->


2、java.lang.OutOfMemoryError: Java heap space

這種內存溢出是最常見的情況之一,主要體現在堆內存的溢出,產生的原因可能是:
1) 設置的內存參數過小(ms/mx, NewSize/MaxNewSize);
2) 程序問題:
Heap space其默認空間(即-Xms)是物理內存的1/64,最大空間(-Xmx)是物理內存的1/4。如果內存剩餘不到40%,JVM就會增大堆到Xmx設置的值,內存剩餘超過70%,JVM就會減小堆到Xms設置的值。所以服務器的Xmx和Xms設置一般應該設置相同避免每次GC後都要調整虛擬機堆的大小。假設物理內存無限大,那麼JVM內存的最大值跟操作系統有關,一般32位機是1.5g到3g之間,而64位的就不會有限制了。

解決方案:可在配置文件中設置堆內存相關參數:

<jvm-arg>-Xms512m</jvm-arg>
<jvm-arg>-Xmx512m</jvm-arg>
<jvm-arg>-Xmn128m</jvm-arg>
<jvm-arg>-XX:MaxNewSize=256m</jvm-arg>

3、java.lang.StackOverflowError

線程棧是線程獨有的一塊內存結構,所以線程棧發生問題必定是某個線程運行時產生的錯誤。一般線程棧溢出是由於遞歸太深或方法調用層級過多導致的。

public class StackOverflowTest {   
    public static void main(String[] args) {  
        int i =0;  
        digui(i);  
    }  
    private static void digui(int i){  
        System.out.println(i++);  
        String[] s = new String[50];  
        digui(i);  
    }  
  }
解決方案:代碼儘量避免死循環或循環產生過多重複的對象實體







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