2010-06-09 44 views
0

我有一個服務器在爲每個連接的用戶創建一個新線程,但是在服務器上有6個人超過15分鐘後,它往往會失敗並給我java堆內存不足錯誤我有1線程每隔30秒檢查一次mysql數據庫,看看當前登錄的用戶是否有任何新消息。什麼是實現服務器隊列的最簡單方法?使服務器隊列最簡單也是最好的方法java

這是我的服務器我的主要方法:

public class Server { 

    public static int MaxUsers = 1000; 
    //public static PrintStream[] sessions = new PrintStream[MaxUsers]; 
    public static ObjectOutputStream[] sessions = new ObjectOutputStream[MaxUsers]; 
    public static ObjectInputStream[] ois = new ObjectInputStream[MaxUsers]; 
    private static int port = 6283; 
    public static Connection conn; 
     static Toolkit toolkit; 
    static Timer timer; 

    public static void main(String[] args) { 
     try { 
      conn = (Connection) Mysql.getConnection(); 
     } catch (Exception ex) { 
      Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex); 
     } 
     System.out.println("****************************************************"); 
     System.out.println("*             *"); 
     System.out.println("*     Cloud Server     *"); 
     System.out.println("*      ©2010      *"); 
     System.out.println("*             *"); 
     System.out.println("*     Luke Houlahan     *"); 
     System.out.println("*             *"); 
     System.out.println("* Server Online         *"); 
     System.out.println("* Listening On Port " + port + "       *"); 
     System.out.println("*             *"); 
     System.out.println("****************************************************"); 
     System.out.println(""); 
     mailChecker(); 
     try { 
      int i; 
      ServerSocket s = new ServerSocket(port); 
      for (i = 0; i < MaxUsers; ++i) { 
       sessions[i] = null; 
      } 
      while (true) { 
       try { 
        Socket incoming = s.accept();      
        boolean found = false; 
        int numusers = 0; 
        int usernum = -1; 
        synchronized (sessions) { 
         for (i = 0; i < MaxUsers; ++i) { 
          if (sessions[i] == null) { 
           if (!found) { 
            sessions[i] = new ObjectOutputStream(incoming.getOutputStream()); 
            ois[i]= new ObjectInputStream(incoming.getInputStream()); 
            new SocketHandler(incoming, i).start(); 
            found = true; 
            usernum = i; 
           } 
          } else { 
           numusers++; 
          } 
         } 
         if (!found) { 
          ObjectOutputStream temp = new ObjectOutputStream(incoming.getOutputStream()); 
          Person tempperson = new Person(); 
          tempperson.setFlagField(100); 
          temp.writeObject(tempperson); 
          temp.flush(); 
          temp = null; 
          tempperson = null; 
          incoming.close(); 
         } else { 
         } 
        } 
       } catch (IOException ex) { 
        System.out.println(1); 
        Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex); 
       } 
      } 
     } catch (IOException ex) { 
      System.out.println(2); 
      Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex); 
     } 
    } 
     public static void mailChecker() { 
     toolkit = Toolkit.getDefaultToolkit(); 
     timer = new Timer(); 
     timer.schedule(new mailCheck(), 0, 10 * 1000); 
    } 
} 
+0

請勿使用[code]。只需粘貼您的代碼,將其標記並單擊工具欄上的代碼按鈕即可正確格式化。 – ZeissS 2010-06-09 21:57:14

+0

SocketHandler的外觀如何?你說它在6個用戶之後失效,每個用戶是否保持永久連接,還是重新連接多次? – mdma 2010-06-09 22:12:39

+0

佔空比連接。需要否則服務器不會允許我發送對象向後轉發到特定客戶端 – user322406 2010-06-09 22:15:13

回答

1

好像你有內存泄漏。 6個線程並不多。我懷疑這是因爲ObjectInputStream和ObjectOutputStream緩存了所有傳輸的對象。這使得他們很不適合長期轉移。你認爲你正在發送一個被gc化的對象,但它真的被對象流保存在內存中。

要刷新流緩存,使用

objectOutputStream.reset() 

writeObject()

編輯寫你的對象後: 要獲得線程池,該SocketHandler可以傳遞給一個Executor而不是啓動它自己的線程。你創建了一個執行者:

Executor executor = Executors.newFiexThreadPool(MaxUsers); 

被創建爲一個場,或者在同一水平作爲服務器套接字執行人。然後 接受連接後,您添加的SocketHandler執行人:

executor.execute(new SocketHandler(...)); 

但是,如果你的客戶是長住,那麼這將使幾乎沒有改善,因爲相較於工作量線程的啓動時間是小在每個線程上完成。池對於執行許多小任務是最有效的,而不是幾個大任務。

至於使服務器更強大的 - 一些快速提示

  • 確保它開始與足夠的內存,或者至少是最大內存設置爲預期1000個用戶的需要。
  • 使用一個負載測試框架,例如Apache JMeter來驗證它將擴展到最大用戶數。
  • 爲您的數據庫使用連接池,不要手動編寫JDBC調用 - 使用已建立的框架Spring JDBC。
  • 默認情況下,每個線程以2MB堆棧開始。所以,如果你有1000個用戶,那麼這個堆棧就會使用〜2GB的虛擬進程空間。在許多32位系統上,這是您可以擁有的用戶空間量,因此不會有數據空間。如果您需要更多用戶,那麼可以擴展到更多進程,負載平衡器將請求傳遞給每個進程,或者查看每個連接不需要線程的服務器解決方案。
  • 注意細節,特別是異常處理。
  • 記錄,用於診斷故障。
  • JMX或其他可管理性來監視服務器運行狀況,當值超出限制時會通知您(例如,內存/ cpu使用時間過長或請求時間太慢。)

Architecture of a Highly Scalable Server

+0

賓果!接得好。 @houlahan:你使用ObjectInputStream和ObjectOutputStream類的方式存在內存泄漏。 – ssahmed555 2010-06-09 22:35:50

+0

仍然如果我想要成千上萬的連接到這個服務器使線程是高性價比:(需要一些更強大的功能,但仍然能夠發送對象向前和向後轉發到特定的客戶端我環顧四周,但我找到了很多強大的信息Java服務器 – user322406 2010-06-09 23:01:16

+0

謝謝你mdma!這正是我一直在尋找的信息,我會看看使用Apache JMeter和春季JDBC :) – user322406 2010-06-10 08:23:00

0

你應該檢查出的Java NIO構建可伸縮的服務器

+0

NIO只是給你非阻塞I/O,它不是一個神奇的可伸縮性項目符號。 – skaffman 2010-06-09 22:07:39

+0

你還可以使用java nio的對象輸入輸出流嗎?我已經花了很多時間在這個客戶端和服務器上,並且不想重新開始:( – user322406 2010-06-09 22:08:03

+0

這不是一個神奇的子彈,但是如果你使用線程每個線程來構建可擴展的任何東西,客戶端模型 不,我不相信你可以很容易地使用NIO的對象流,我有一個模糊的回憶庫,可以讓你這樣做,但我不記得細節 也許你' d更適合於RMI嗎? – dty 2010-06-09 22:16:56

0

我將注意力集中在爲什麼你正在運行的堆空間不足,或者說爲什麼你的程序得到6個時,連接已經開放了一段時間的OOM錯誤。您的服務器應該能夠擴展到至少更多的併發併發連接,但很難量化這個數字沒有得到有關環境,硬件等多個細節

你只有發佈的主要方法你的服務器,所以很難判斷是否有內存泄漏,資源泄漏等可能導致你用完堆空間。您是否使用默認堆設置運行服務器?如果是這樣,您可能想要嘗試增加堆大小,因爲默認值非常保守。

羅曼是正確的:你應該關閉你的流資源在try { ... } finally { ... }區塊,以確保你沒有泄漏資源。

最後,您可能需要考慮將備份參數傳遞給ServerSocket constructor。這指定了到該ServerSocket的傳入連接的最大隊列大小,之後拒絕任何新連接。但首先,您仍然需要弄清楚爲什麼您的服務器無法處理超過6個連接。

+0

我認爲這是因爲mdma說關於我不重置我目前離開的流我的電腦此刻正在檢查:/當我早上回家時,我會上傳服務器的其餘部分 – user322406 2010-06-09 22:38:58