2015-07-19 82 views
2

我寫了一個Java類,它有一個現有的tcp連接池和一個工作線程,它們在這些連接上循環。在檢查它需要處理來自對等體的信息的函數中,它調用了inputStream.available()。我正在看內存跟蹤器,並且它在不斷上升。當我錄製分配有在Java/Android SDK內存泄漏使用inputStream.available()

at libcore.io.IoBridge.available(IoBridge.java:57) 
at java.net.PlainSocketImpl.available(PlainSocketImpl.java:128) 
at java.net.PlainSocketImpl$PlainSocketInputStream.available(PlainSocketImpl.java:225) 
at myClass 

有問題的代碼分配幾百android.util.MutableInt對象:

void someFunction() { 
    (new Thread(){ 
     public void run() { 
      try { 
       while(true) { 
        for(int i=0; i<peers.size(); i++) { 
         peer = peers.get(i); 
         peer.doWorkListen(); 
         peer.doWorkSend(); 
        } 
        Thread.sleep(1); 
       } 
      catch (Exception e) {} 
     }).start(); 
} 

void peer.doWorkListen() { 
    if(inputStream.available() > 0) { 
     //Read and process input 
    } 
} 
+0

你能否提供可疑代碼 – ydobonebi

+0

@QuinnRoundy我已經添加了有問題代碼的要點。 – David

+0

擺脫'available()'測試和睡眠併爲每個連接使用一個讀取線程。這是不好的技術。 – EJP

回答

1

什麼,你遇到的是不是內存泄漏,而是垃圾收集器無法以跟上你每秒創建的大量垃圾對象*最終,你會發現你的應用程序暫時大量凍結,因爲gc調用停止世界的技術來批量刪除不需要的對象。

從您的代碼中,我可以看到您正在嘗試使用單個線程輪詢來自多個流的可用性。我認爲你在這裏創建的是Java/Android非阻塞I/O的一個非常簡單和低效的實現。

鑑於堆棧跟蹤,我也假設你在這種情況下使用套接字。使用ServerSocketChannelSocketChannel以及Selector可以爲您提供所需的功能。 選擇器和SocketChannel只有的示例實現在下面提供(如果這是服務器端代碼,則應該使用相同邏輯實現ServerSocketChannel)。

public class Foo implements Runnable{ 
    private final Selector selector; 
    private volatile boolean run; 

    public Foo() throws IOException{ 
     selector = Selector.open(); 
     run = true; 
    } 

    public void registerChannel(SocketChannel channel) throws IOException{ 
     channel.configureBlocking(false); 
     // Optionally use a selection key for write as well 
     channel.register(selector, SelectionKey.OP_READ); 
    } 

    public void shutdown(){ 
     run = false; 
     selector.wakeup(); 
     try{ 
      selector.close(); 
     }catch(IOException ignore){} 
    } 

    public void run(){ 
     while(run){ 
      try{ 
       int readyCount = selector.select(); 

       // Selector was interrupted or manually woken up 
       if(readyCount == 0){ 
        // handle appropriately 
       }else{ 
        Iterator<SelectionKey> iterKeys = selector.selectedKeys().iterator(); 

        while(iterKeys.hasNext()){ 
         SelectionKey key = iterKeys.next(); 

         if(key.isReadable()){ 
          SocketChannel chn = (SocketChannel) key.channel(); 

          // Process input here 
         }else{ 
          // We aren't interested in non-readable channels atm 
         } 

         // Very important 
         iterKeys.remove(); 
        } 
       } 
      }catch(IOException ex) { 
       // handle selector exception 
      } 
     } 
    } 
} 

Foo充當可運行來創建一個線程並多路複用SocketChannel以有效的方式,阻斷(相對於快速輪詢)插座,直到一個是可用的。這不僅消除了大量對象創建的問題,還消除了在不同平臺上可能不可靠的available() **的使用。這種設計模式還帶來了額外的好處,因爲它不必按順序遍歷套接字列表,因此可能比使用大量套接字的方法更快。

查看關於SelectorSocketChannel以及alternative example的教程以獲得更深入的瞭解。


*我只能追溯至available()IoBridge.available(fd)執行(我有沒有IoBridge來源),因此內存泄漏不能完全排除,雖然這是極不可能的。

** available()的實現差別很大,不應該用作有多少數據可用的絕對指標。從我發現的情況來看,java的PlainSocketImpl硬編碼對套接字返回0。即使android通過IoBridge.available(fd)有一個有效的available(),如果事實證明確實是libcore中的內存泄漏,您也可以避開非阻塞I/O的問題。

+1

我花了一段時間來學習新的範例並徹底改變了我的許多代碼,但是我已經使用它併合理地使用了內存。謝謝! – David