為什么JAVA的垃圾回收機制無法避免內存泄漏


一、本文參考:
   1.《深入理解java虛擬機 JVM高級特性與最佳實踐》
 
二、對象已死的判定方法
      要進行JVM中對象回收首先要判斷對象是否已經死亡,判斷的方法有如下幾個:
     1.引用計數法
         給對象中添加一個引用計數器,每當有一個地方引用它時,計數器值就加1;當引用失效時,計數器值就減1;任何時刻 計數器為0的對象就是不可能再被使用的。
 但是主流的java虛擬機里面沒有選用引用計數器算法來管理內存,其中最主要的原因是它很難解決對象之間相互循環引用的問題。
 
     2.可達性分析算法
     這個算法的基本思想就是通過一系列的稱為“GC Roots"的對象作為起始點,從這些節點開始向下搜索,搜索所走過的路徑稱為引用鏈,當一個對象到GC Roots沒有任何引用鏈相連接時,則證明此對象是不可用的。如下圖所示,對象object5、object6、object7雖然互相有關聯,但是它們到GC Roots是不可達的,所以它們將會被判定為是可回收對象。
 
 
三、導致內存泄漏的情況及代碼
 
java 堆內存泄漏。是由於java對象不停創建但是沒有釋放對象引用導致的。
以下是關於java代碼,此代碼是引自 http://coderevisited.com/memory-leaks-in-java/
 
類com.code.revisited.memoryleaks.Stack提供了實現棧的一些方法,包括遍歷,入棧,出棧等操作。假設原來目的是為了現實使用(當然這里是為了解釋內存泄漏)。
 
package com.code.revisited.memoryleaks;

import java.util.Iterator;
import java.util.NoSuchElementException;

/**
 * @author sureshsajja
 * 
 */
public class Stack<E> implements Iterable<E> {

    private int N;
    private E[] array;

    @SuppressWarnings("unchecked")
    public Stack(int capacity) {
        array = (E[]) new Object[capacity];
    }

    @Override
    public Iterator<E> iterator() {
        return new StackIterator();
    }

    private class StackIterator implements Iterator<E> {

        private int i = N - 1;

        @Override
        public boolean hasNext() {
            return i >= 0;
        }

        @Override
        public E next() {
            if (!hasNext()) {
                throw new NoSuchElementException();
            }
            return array[i--];
        }

        @Override
        public void remove() {
            throw new UnsupportedOperationException();

        }

    }

    public void push(E item) {
        if (isFull()) {
            throw new RuntimeException("Stack overflow");
        }
        array[N++] = item;
    }

    public E pop() {
        if (isEmpty())
            throw new RuntimeException("Stack underflow");
        E item = array[--N];
        return item;
    }

    public boolean isEmpty() {
        return N == 0;
    }

    public int size() {
        return N;
    }

    public boolean isFull() {
        return N == array.length;
    }

    public E peek() {
        if (isEmpty())
            throw new RuntimeException("Stack underflow");
        return array[N - 1];
    }

}

類com.code.revisited.memoryleaks.StackTest用於執行棧操作。要進行入棧及出棧10000次操作,理想是入棧時分配堆內存,出棧后對象被回收。

package com.code.revisited.memoryleaks;

/**
 * @author  sureshsajja
 *
 */
public class StackTest {

        /**
        * @param args
        */
        public static void main(String[] args) {
              Stack<Integer> s = new Stack<Integer>(10000);
               for (int i = 0; i < 10000; i++) {
                     s.push(i);
              }

               while (!s.isEmpty()) {
                     s.pop();
              }
               while (true ) {
                      // do something
              }

       }

}
執行開始。我們使用VisualVM進行觀察。為了更明顯一些,將棧操作部分代碼注釋也執行一下。
package com.code.revisited.memoryleaks;

/**
 * @author  sureshsajja
 *
 */
public class StackTest {

        /**
        * @param args
        */
        public static void main(String[] args) {
//            Stack<Integer> s = new Stack<Integer>(10000);
//            for ( int i = 0; i < 10000; i++) {
//                   s.push(i);
//            }
//
//            while (!s.isEmpty()) {
//                   s.pop();
//            }
               while (true ) {
                      // do something
              }

       }

}

把棧操作的設為1號,沒有棧操作的設置為2號,分別生成Heap Dump文件,我們看一下類實例的截圖:

首先是1號截圖
首先是2號截圖
顯然預期的棧操作出棧后並沒有釋放掉Integer對象的引用(實際上看代碼也知道),所以不會被GC回收。真正的實際情況這種引用將會很隱蔽,但是根本總是由於對象仍然被引用。
  
四、結語
  本篇僅對java堆內存泄漏進行了簡單說明,下一篇將討論其他相關的內存泄漏。有不對的地方歡迎拍磚>_<


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM