2012-04-02 42 views
2

當我運行在探查下面的代碼,我得到一個char []和byte []是累積,直到程序崩潰由於Java堆內存溢出異常。有人能告訴我爲什麼嗎?也許我正在做一些根本錯誤的事情。噩夢java的泄漏...帶環和JDBC

package testleak; 
import java.awt.event.ActionEvent; 
import java.awt.event.ActionListener; 
import java.sql.Connection; 
import java.sql.DriverManager; 
import java.sql.ResultSet; 
import java.sql.SQLException; 
import java.sql.Statement; 
import java.util.Properties; 
import javax.swing.Timer; 

    public class TestLeak 
    { 
     static String DB_USERNAME = "userName"; 
     static String DB_SUBSCRIPTION_EXPIRATION = "subscriptionExpiration"; 
     static String DB_REMOTE_ACCESS_ENABLED = "remoteAccessEnabled"; 
     static String DB_LOCAL_USERNAME = "root"; 
     static String DB_LOCAL_PASS = "root"; 
     public static void main(String[] args) 
     { 
      Timer timer = new Timer(2000, new ActionListener() 
      { 
       @Override 
       public void actionPerformed(ActionEvent evt) 
       { 
        TestLeak tester = new TestLeak(); 
        try 
        { 
         tester.go(); 
        } 
        catch (NumberFormatException n) 
        { 
        } 
        tester = null; 
       } 
      }); 
      timer.start(); 
      while (true) 
      { 
       //keep the program from ending... 
      } 

     } 
     private void go() throws NumberFormatException 
     { 
      ResultSet results = null; 
      Connection conn = null; 
      Properties connectionProps = new Properties(); 
      try 
      { 
       connectionProps.put("user", "root"); 
       connectionProps.put("password", "root"); 
       conn = DriverManager.getConnection("jdbc:mysql://localhost:8889/myDataBase", 
         connectionProps); 
       connectionProps = null; 
       try 
       { 
        String rawQuery = new String("SELECT " + TestLeak.DB_USERNAME + ", " 
          + TestLeak.DB_REMOTE_ACCESS_ENABLED 
          + ", " + TestLeak.DB_SUBSCRIPTION_EXPIRATION + " FROM myTable"); 
        Statement statement = conn.createStatement(); 
        try 
        { 
         statement.executeQuery(rawQuery); 
         results = statement.getResultSet(); 
         rawQuery = null; 
         try 
         { 
          while (results.next()) 
          { 
           String auth = new String(results.getString(TestLeak.DB_REMOTE_ACCESS_ENABLED)); 
           if (auth.equals("1")) 
           { 
            Long subExpires = Long.valueOf(results.getString(TestLeak.DB_SUBSCRIPTION_EXPIRATION)); 
            if (subExpires > System.currentTimeMillis()) 
            { 
             System.out.println(results.getString(TestLeak.DB_USERNAME)); 
             System.out.println(); 
            } 
            subExpires = null; 
           } 
           auth = null; 
          } 
         } 
         finally 
         { 
          results.close(); 
         } 
        } 
        finally 
        { 
         statement.close(); 
        } 
       } 
       finally 
       { 
        conn.close(); 
       } 
      } 
      catch (SQLException e) 
      { 
       System.out.println(e.getMessage()); 
      } 
     } 
    } 

我想我釋放一切,但必須防止所有對象被釋放。爲什麼go()方法結束時,所有對象都不符合垃圾回收的條件?每次我在分析器中啓動垃圾回收時,我都會得到另一代倖存者。 謝謝。

+1

所以......把它放在調試器中,弄清楚什麼是錯誤的。 – 2012-04-02 21:14:41

+0

SQLException catch塊位於清理JDBC資源的finally塊之外。你是否得到任何SQLExceptions?在附註中,有一些方法可以清理JDBC資源,而不必像這樣嵌套try/catch/finally塊,這會使代碼更容易閱讀。 – rfeak 2012-04-02 21:19:07

+0

確保那些'.close'實際上被調用。另外,我認爲那些「接近」的聲明需要在他們自己的try-catch中進行,否則第一次失敗會導致剩下的不執行?我相信你的情況下,你應該在'catch'下面有一個'finally'並且處理(你的'ResultSet','Statement'和'Connection')的清理。 – nevets1219 2012-04-02 21:19:45

回答

2

遺憾的是沒有指定的一些細節有關的問題,例如,如何大是結果集(行#),以及它需要多長時間才能出現內存異常。

我沒有訪問權現在你有MySQL驅動程序,但我跑你相同的代碼與數據庫H2,與在mytable的1000行。在測試過程中,JVM的堆大小是穩定的,沒有任何內存泄漏。你可以在附加的截圖中看到。 堆大小增加了一點,然後在GC之後回到原始位置,再次向下,以非常穩定的模式回落。

你可以運行你的應用程序,然後運行Jvisualvm並連接到您的應用程序看,例如,如果從數據庫中結果的數量太大,融入現有的內存。這是我的猜測。在這種情況下,藍線將快速超過最大內存。

如果出現這種情況,請使用-Xmx設置運行應用程序以增加內存大小。

如果確實存在內存泄漏,它不在您的代碼中,而是在您正在使用的驅動程序中。爲了確認內存泄漏,下圖中的藍色線將上升(分配內存),GC將運行(釋放內存),但藍線永遠不會回到原來的位置,留下一些對象。

JVisualVM Screenshot

3

我會改變這樣的:

     statement.executeQuery(rawQuery); 
         results = statement.getResultSet(); 

這樣:

     results = statement.executeQuery(rawQuery); 

後者肯定是API認證的方式做到這一點,雖然我不能肯定的說前者是一個問題,它當然似乎似乎像它可以創建兩個單獨的結果集,其中你只關閉一個。

+0

'getResultSet()'返回當前的ResultSet,所以我非常確定不會創建兩個ResultSet。請參閱http://docs.oracle.com/javase/1.4.2/docs/api/java/sql/Statement.html – nevets1219 2012-04-02 21:22:56

+1

@ nevets1219:該文檔中提到「每個結果只應調用一次該方法」,並且其參見 - 也指向「執行」。所以我認爲調用'executeQuery'會將調用'execute'加上調用'getResultSet',這樣調用'executeQuery'和調用'getResultSet'將會違背API。 – ruakh 2012-04-02 21:26:23

+0

我只是試過多次調用它,每次返回相同的對象 - 它也是從'statement.executeQuery(...)'返回的同一個對象。也許還有其他一些原因,爲什麼它不應該被稱爲多次?我仍然認爲它不會導致多個ResultSet被創建。 – nevets1219 2012-04-02 21:34:51

0

我建議你要做兩件事情:

擴展你的計時器到約10秒。二人對於一個緩慢的系統期待很多。

將一個Thread.currentThread.sleep(10)(或類似)在你的空閒循環。

我希望你是不是在等待go完成。當你在空閒循環中旋轉時,數據庫連接因缺少循環而消亡,每隔兩秒鐘又添加一個連接和查詢。難怪這個可憐的東西正在掙扎。

+0

我不認爲是這樣。我拿出了定時器,並將go()語句放在while(true)循環中,並且我仍然得到不斷增長的char []和byte []。奇怪的是,內存堆看起來很穩定。 – rob345 2012-04-02 22:49:00

+0

我使用以下驅動程序:mysql-connector-java-5.1.11-bin.jar。這可能是問題的根源嗎? – rob345 2012-04-02 23:01:44

+0

如果「內存堆看起來很穩定」,那麼你不會泄漏。 – OldCurmudgeon 2012-04-03 09:34:38