2012-04-02 30 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