iT邦幫忙

2024 iThome 鐵人賽

DAY 7
0
Software Development

深入淺出Java 30天系列 第 7

Day 7: 清除陳舊的object references

  • 分享至 

  • xImage
  •  

如果使用的是C或C++語言,開發人員需要自己管理memory,但當轉換到有garbage collection的程式語言時,會突然覺得工作變輕鬆。但是有garbage collection機制,真的就可以完全不注意memory的使用嗎?不會有memory leak的問題嗎?

以下面使用array實作stack的程式碼為例,並用for迴圈push和pop 100個物件,當物件被pop出stack之後,在沒有做額外的處理情況下,因為每個物件被array reference,所以garbage collector以為它還有被使用,就沒有做garbage collection,結果這些已經被pop出去的物件,被遺留在記憶體中。簡單地用Runtime API計算佔用的記憶體容量,大約是26674776 bytes。

import java.lang.Exception;
import java.util.Arrays;

public class Stack {
    private Object[] items;
    private int size = 0;
    private static final int DEFAULT_SIZE = 16;

    public Stack() {
        items = new Object[DEFAULT_SIZE];
    }

    public void push(Object item) {
        ensureCapacity();
        items[size++] = item;
    }

    public Object pop() throws Exception {
        if (size == 0) {
            throw new Exception("Stack is empty");
        }
        Object result = items[--size];
        return result;
    }

    private void ensureCapacity() {
        if (size == items.length) {
            items = Arrays.copyOf(items, 2 * size + 1);
        }
    }

    public static void main(String[] args) throws Exception {
        long beforeUsedMem=Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory();
        Stack example = new Stack();
        for (int i = 0; i < 100; i++) {
            example.push(new byte[1024 * 1024]);
            example.pop();
        }
        long afterUsedMem=Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory();
        long actualMemUsed=afterUsedMem-beforeUsedMem;
        System.out.println(actualMemUsed);
    }
}


而已經pop出去的物件,可以做什麼處理,避免記憶體的佔用呢?可以在pop的時候,把物件設為null ,這樣就可以消滅遺留的reference,解放記憶體。把物件設為null之後,再重新計算佔用的記憶體容量,大約是4637976 bytes,是沒有消滅遺留的reference的1/5左右。

import java.lang.Exception;
import java.util.Arrays;

public class Stack {
    private Object[] items;
    private int size = 0;
    private static final int DEFAULT_SIZE = 16;

    public Stack() {
        items = new Object[DEFAULT_SIZE];
    }

    public void push(Object item) {
        ensureCapacity();
        items[size++] = item;
    }

    public Object pop() throws Exception {
        if (size == 0) {
            throw new Exception("Stack is empty");
        }
        Object result = items[--size];
        items[size] = null;
        return result;
    }

    private void ensureCapacity() {
        if (size == items.length) {
            items = Arrays.copyOf(items, 2 * size + 1);
        }
    }

    public static void main(String[] args) throws Exception {
        long beforeUsedMem=Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory();
        Stack example = new Stack();
        for (int i = 0; i < 100; i++) {
            example.push(new byte[1024 * 1024]);
            example.pop();
        }
        long afterUsedMem=Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory();
        long actualMemUsed=afterUsedMem-beforeUsedMem;
        System.out.println("Memory used: " + actualMemUsed + " bytes");
    }
}

當然,使用null 清除reference這個方法,只能當不得已使用的workaround,因為如果不小心把正在被使用的reference設為null,很容易造成NullPointerException。

除了上面的範例會有memory leak的風險,cache也常常會有memory leak的問題,因為使用cache時,裡面的record是由開發人員管理,不是由JVM管理,所以有一些沒有被使用的record,很有可能被一直遺忘在cache裡面。所以在實作cache的時候,可以使用WeakHashMap ,這樣當裡面的資料沒有被reference的時,就會自動被清理,當然,也可以設置expire time,讓tool(ex: redis)自動去清理過期的record。


上一篇
Day 6: 避免生成不必要的物件
下一篇
Day 8: 避免使用finalizers
系列文
深入淺出Java 30天12
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言