Bug #34518 Memory leak: StatementImpl.execute() leaks the executed sql string.
Submitted: 13 Feb 2008 11:08 Modified: 25 Feb 2008 12:12
Reporter: Shadow Caster Email Updates:
Status: Closed Impact on me:
None 
Category:Connector / J Severity:S2 (Serious)
Version: OS:Any
Assigned to: Mark Matthews CPU Architecture:Any

[13 Feb 2008 11:08] Shadow Caster
Description:
The method "StatementImpl.createResultSetUsingServerFetch" creates a "JDBC4ServerPreparedStatement" which it never closes. The statement registers itself with the connection when it is created and remains in the "openStatements" table forever.

What makes it worse is that it keeps a reference to the executed sql string, which might be really really large. The memory leak occurs in the default configuration and is likely to happen only in production systems, not with small test data, so it's kind of nasty.

How to repeat:
Connection c = ...;

for (;;) {
  Statement s = c.createStatement();
  s.execute("INSERT INTO SomeTable VALUES(x'00000000000....')"); // many zeros
  s.close();
}

Versions:
Connector: mysql-connector-java-5.1.5.zip
Engine: mysql-6.0.3-alpha-win32.zip

Suggested fix:
Disabling resource tracking ("dontTrackOpenResources") serves as a workaround.

Perhaps the JDBC4ServerPreparedStatement should not be registered at all, because it is not created explicitly by a user.
[13 Feb 2008 11:12] Tonci Grgin
Hi and thanks for your report. I think this behavior is intentional but will have to check.
[13 Feb 2008 11:37] Shadow Caster
As it turns out, this does not happen in the default configuration. It only occurs when "useCursorFetch" is enabled.
[13 Feb 2008 13:43] Shadow Caster
It also requires setFetchSize. To prevent that any ohter condition is missing, I have created a complete test case.

You should see OutOfMemoryError within a few seconds. Additionally, increasing the size of the blob to like 100k causes the mysql server itself to start using insane amounts of memory.

public static void main(String[] args) {
  try {
    Class.forName("com.mysql.jdbc.Driver");
    Properties p = new Properties();
    p.put("user", "root");
    p.put("password", "***");
    p.put("useCursorFetch", "true");

    Connection c = DriverManager.getConnection(
        "jdbc:mysql://localhost:3306/", p);

    Statement s = c.createStatement();
    s.execute("CREATE DATABASE memtest");
    s.execute("USE memtest");
    s.execute("CREATE TABLE foo (bar LONGBLOB) Engine=MyISAM");
    s.close();

    char[] zero = new char[1000];
    Arrays.fill(zero, '0');
    String sql = "INSERT INTO foo VALUES (x'" + new String(zero) + "')";

    for (;;) {
      Statement t = c.createStatement();
      t.setFetchSize(10);
      t.execute(sql);
      t.close();
     }
   } catch (Exception exc) {
     exc.printStackTrace();
   }
}
[13 Feb 2008 13:58] Tonci Grgin
Verified as described.
[13 Feb 2008 14:06] Bugs System
A patch for this bug has been committed. After review, it may
be pushed to the relevant source trees for release in the next
version. You can access the patch from:

  http://lists.mysql.com/commits/42217
[13 Feb 2008 14:14] Mark Matthews
Because of the way cursors work in MySQL, we do have to hold onto the "internal" prepared statement until the result set is closed, so that's what this fix does.

The string used for the statement will be held as well, because we have to have the ability to "re-prepare" the statement should we lose the connection. Without the original SQL, it can not be done.
[25 Feb 2008 12:12] MC Brown
A note has been added to the 5.1.6 changelog: 

When using a cursor fetch for a statement, the internal prepared statement could cause a memory leak until the connection was closed. The internal prepared statement is now deleted when the corresponding result set is closed.
[27 Feb 2008 17:12] Bugs System
A patch for this bug has been committed. After review, it may
be pushed to the relevant source trees for release in the next
version. You can access the patch from:

  http://lists.mysql.com/commits/43086