1. 程式人生 > >兩道2017華為校招Java面試題

兩道2017華為校招Java面試題

 void method(){
        Vector vector = new Vector();
        for (int i = 1; i<100; i++)
        {
            Object object = new Object();
            vector.add(object);
            object = null;
        }
        //...對v的操作
        vector = null;
        //...與v無關的其他操作
    }

上面Vector已經過時了,不過只是使用老的例子來做記憶體洩露的介紹。我們使用容器時很容易發生記憶體洩露,就如上面的例子,不過上例中,容器時方法內的區域性變數,造成的記憶體洩漏影響可能不算很大(但我們也應該避免),但是,如果這個容器作為一個類的成員變數,甚至是一個靜態(static)的成員變數時,就要更加註意記憶體洩露了。

    下面也是一種使用容器時可能會發生的錯誤:

public class CollectionMemory {
    public static void main(String s[]){
        Set<MyObject> objects = new LinkedHashSet<MyObject>();
        objects.add(new MyObject());
        objects.add(new MyObject());
        objects.add(new MyObject());
        System.out.println(objects.size());
        while(true){
            objects.add(new MyObject());
        }
    }
}
class MyObject{
    //設定預設陣列長度為99999更快的發生OutOfMemoryError
    List<String> list = new ArrayList<>(99999);
}
執行上面的程式碼將很快報錯:

3
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.ArrayList.<init>(ArrayList.java:152)
    at com.anxpp.memory.MyObject.<init>(CollectionMemory.java:21)
    at com.anxpp.memory.CollectionMemory.main(CollectionMemory.java:16)

如果足夠了解Java的容器,上面的錯誤是不可能發生的。這裡也推薦一篇本人介紹Java容器的文章:...

    容器Set只存放唯一的元素,是通過物件的equals()方法來比較的,但是Java中所有類都直接或間接繼承至Object類,Object類的equals()方法比較的是物件的地址,上例中,就會一直新增元素直到記憶體溢位。

    所以,上例嚴格的說是容器的錯誤使用導致的記憶體溢位。

    就Set而言,remove()方法也是通過equals()方法來刪除匹配的元素的,如果一個物件確實提供了正確的equals()方法,但是切記不要在修改這個物件後使用remove(Object o),這也可能會發生記憶體洩露。

    各種提供了close()方法的物件

    比如資料庫連線(dataSourse.getConnection()),網路連線(socket)和io連線,以及使用其他框架的時候,除非其顯式的呼叫了其close()方法(或類似方法)將其連線關閉,否則是不會自動被GC回收的。其實原因依然是長生命週期物件持有短生命週期物件的引用。

    可能很多人使用過hibernate,我們操作資料庫時,通過SessionFactory獲取一個session:

Session session=sessionFactory.openSession();
完成後我們必須呼叫close()方法關閉:
session.close();

 SessionFactory就是一個長生命週期的物件,而session相對是個短生命週期的物件,但是框架這麼設計是合理的:它並不清楚我們要使用session到多久,於是只能提供一個方法讓我們自己決定何時不再使用。

    因為在close()方法呼叫之前,可能會丟擲異常而導致方法不能被呼叫,我們通常使用try語言,然後再finally語句中執行close()等清理工作:

 try{
        session=sessionFactory.openSession();
        //...其他操作
    }finally{
        session.close();
    }

   單例模式導致的記憶體洩露

    單例模式,很多時候我們可以把它的生命週期與整個程式的生命週期看做差不多的,所以是一個長生命週期的物件。如果這個物件持有其他物件的引用,也很容易發生記憶體洩露。

    內部類和外部模組的引用

    其實原理依然是一樣的,只是出現的方式不一樣而已。

與清理相關的方法

    本節主要談論gc()和finalize()方法。

    gc()

    對於程式設計師來說,GC基本是透明的,不可見的。執行GC的函式是System.gc(),呼叫後啟動垃圾回收器開始清理。

    但是根據Java語言規範定義, 該函式不保證JVM的垃圾收集器一定會執行。因為,不同的JVM實現者可能使用不同的演算法管理GC。通常,GC的執行緒的優先級別較低。

    JVM呼叫GC的策略也有很多種,有的是記憶體使用到達一定程度時,GC才開始工作,也有定時執行的,有的是平緩執行GC,有的是中斷式執行GC。但通常來說,我們不需要關心這些。除非在一些特定的場合,GC的執行影響應用程式的效能,例如對於基於Web的實時系統,如網路遊戲等,使用者不希望GC突然中斷應用程式執行而進行垃圾回收,那麼我們需要調整GC的引數,讓GC能夠通過平緩的方式釋放記憶體,例如將垃圾回收分解為一系列的小步驟執行,Sun提供的HotSpot JVM就支援這一特性。

    finalize()

    finalize()是Object類中的方法。

    瞭解C++的都知道有個解構函式,但是注意,finalize()絕不等於C++中的解構函式。

    Java程式設計思想中是這麼解釋的:一旦GC準備好釋放物件所佔用的的儲存空間,將先呼叫其finalize()方法,並在下一次GC回收動作發生時,才會真正回收物件佔用的記憶體,所以一些清理工作,我們可以放到finalize()中。

    該方法的一個重要的用途是:當在java中呼叫非java程式碼(如c和c++)時,在這些非java程式碼中可能會用到相應的申請記憶體的操作(如c的malloc()函式),而在這些非java程式碼中並沒有有效的釋放這些記憶體,就可以使用finalize()方法,並在裡面呼叫本地方法的free()等函式。

    所以finalize()並不適合用作普通的清理工作。

    不過有時候,該方法也有一定的用處:

    如果存在一系列物件,物件中有一個狀態為false,如果我們已經處理過這個物件,狀態會變為true,為了避免有被遺漏而沒有處理的物件,就可以使用finalize()方法:

class MyObject{
    boolean state = false;
    public void deal(){
        //...一些處理操作
        state = true;
    }
    @Override
    protected void finalize(){
        if(!state){
            System.out.println("ERROR:" + "物件未處理!");
        }
    }
    //...
}

 但是從很多方面瞭解,該方法都是被推薦不要使用的,並被認為是多餘的。
 總的來說,記憶體洩露問題,還是編碼不認真導致的,我們並不能責怪JVM沒有更合理的清理。