【问题标题】:Nightmare java leak... with loop and jdbc噩梦 java 泄漏...带有循环和 jdbc
【发布时间】:2012-04-16 13:16:36
【问题描述】:

当我在分析器中运行以下代码时,我会得到一个 char[] 和 byte[] ,直到程序由于 java 堆内存不足异常而崩溃。有人能告诉我为什么吗?也许我在做一些根本错误的事情。

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() 方法结束时所有对象都没有资格进行垃圾回收?每次我在分析器中调用垃圾收集时,我都会得到另一代幸存者。 谢谢。

【问题讨论】:

  • 所以...把它放在调试器中,找出问题所在。
  • SQLException 捕获块位于清理 JDBC 资源的 finally 块之外。你得到任何 SQLExceptions 吗?附带说明一下,有一些方法可以清理 JDBC 资源,而不必像这样嵌套 try/catch/finally 块。这将使代码更易于阅读。
  • 确保那些.close 确实被调用了。此外,我认为那些“关闭”语句需要在他们自己的 try-catch 中,否则第一次失败会导致其余的不执行?我相信在您的情况下,您应该在您的catch 下方有一个finally,并在那里处理(您的ResultSetStatementConnection)的清理。
  • while(true) 确实是个坏主意,因为它使您的线程保持运行。您应该在同步块中调用 wait()
  • 运行它时我没有遇到任何异常。会不会是while(true)??有人可以给我一个基本示例,说明如何通过调用 wait() 来运行它吗?

标签: java mysql loops jdbc memory-leaks


【解决方案1】:

我会改变这个:

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

到这里:

                        results = statement.executeQuery(rawQuery);

后者当然是 API 批准的方法,虽然我不能肯定地说前者是一个问题,但它肯定 似乎 可以创建两个单独的结果 -集,其中你只关闭一个。

【讨论】:

  • getResultSet() 返回当前的结果集,所以我很确定不会创建两个结果集。见docs.oracle.com/javase/1.4.2/docs/api/java/sql/Statement.html
  • @nevets1219:该文档说“每个结果只应调用此方法一次”,其另见指向execute。所以我认为调用executeQuery算作调用execute加上调用getResultSet,这样调用executeQuery加上调用getResultSet就会违反API。
  • 我只是尝试多次调用它,每次都返回同一个对象 - 它也是从 statement.executeQuery(...) 返回的同一个对象。也许还有其他原因不应该多次调用它?我仍然认为它不会导致创建多个ResultSet
  • @nevets1219:不应多次调用它的原因是 JDBC 驱动程序不需要以任何特定方式处理这种情况。因此,除非您具有与 OP 相同的 JDBC 驱动程序,否则您的调查没有意义。 (可以肯定的是:您似乎很可能确实拥有与 OP 相同的 JDBC 驱动程序。但我不会认为这是理所当然的。)
  • 感谢您,我忘记了 JDBC 驱动程序的区别。我想我会问 SO 看看你为什么不应该多次打电话给getResultSet,我对此很好奇。你认为未定义的行为是唯一的原因吗?
【解决方案2】:

不幸的是,您没有指定有关问题的一些详细信息,例如,结果集有多大(行数),以及遇到内存不足异常需要多长时间。

我现在无法访问您拥有的 mysql 驱动程序,但我使用 H2 数据库运行了相同的代码,myTable 中有 1000 行。 JVM 的堆大小在测试过程中是稳定的,没有任何内存泄漏。您可以在随附的屏幕截图中看到这一点。 堆大小增加了一点,然后在 GC 之后又回到了原来的位置,再次向上,再次向下,非常稳定的模式。

您可以运行您的应用程序,然后运行 ​​Jvisualvm 并连接到您的应用程序以查看,例如,来自数据库的结果数量是否太大而无法放入现有内存。这是我的猜测。在这种情况下,蓝线会迅速超过最大内存。

如果是这种情况,您可以使用 -Xmx 设置运行应用程序以增加内存大小。

如果确实存在内存泄漏,则不在您的代码中,而是在您正在使用的驱动程序中。为了确认内存泄漏,下图中的蓝线会上升(分配内存),GC 会运行(释放内存),但蓝线永远不会回到原来的位置,留下一些对象。

【讨论】:

    【解决方案3】:

    我建议你尝试两件事:

    将计时器延长到大约 10 秒。二是对慢速系统的期望很高。

    在您的空闲循环中放置 Thread.currentThread.sleep(10)(或类似名称)。

    我希望您不会等待go 完成。当您在空闲循环中进行空中旋转时,数据库连接因缺乏循环而死亡,并且每两秒钟您添加另一个连接和查询。难怪可怜的东西在苦苦挣扎。

    【讨论】:

    • 我不这么认为。我拿出计时器,把 go() 语句放在 while(true) 循环中,我仍然得到一个不断增长的 char[] 和 byte[]。不过奇怪的是,内存堆似乎很稳定。
    • 我正在使用以下驱动程序:mysql-connector-java-5.1.11-bin.jar。这可能是问题的根源吗?
    • 如果the memory heap seems stable 那么你没有泄漏。
    【解决方案4】:

    在内存不足 arg 上添加堆转储,然后使用 mat 或类似的方法查看堆。 Using HeapDumpOnOutOfMemoryError parameter for heap dump for JBoss

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多