2

When I run the following code in the profiler, I get a char[] and byte[] that build up until the program crashes due to a java heap out of memory exception. Can someone tell me why? Perhaps I am doing something fundamentally wrong.

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());
            }
        }
    }

I think I am releasing everything, but something must be preventing all objects from being released. Why is it that all objects are not eligible for garbage collection when the go() method ends? Every time I envoke garbage collection in the profiler I get another surviving generation. Thanks.

ruakh
  • 156,364
  • 23
  • 244
  • 282
rob345
  • 193
  • 1
  • 11
  • 1
    So ... put it in the debugger and figure out what's going wrong. – Brian Roach Apr 02 '12 at 21:14
  • The SQLException catch block is outside the finally blocks that clean up JDBC resources. Are you getting any SQLExceptions? On a side note, there are methods for cleaning up JDBC resources without having to nest your try/catch/finally blocks like this.It would make the code much easier to read. – rfeak Apr 02 '12 at 21:19
  • Make sure those `.close` are actually being called. Additionally, I thought those "close" statements needed to be in their own try-catch otherwise the first failure would cause the remaining to not execute? I believe in your case you should have a `finally` below your `catch` and handle the cleanup (of your `ResultSet`, `Statement`, and `Connection`) there. – nevets1219 Apr 02 '12 at 21:19
  • 1
    The while(true) is really a bad idea because it keeps your thread running. You should rather invoke wait() in a synchronized block – Guillaume Polet Apr 02 '12 at 21:20
  • I am not getting any exceptions while running this. Could it be the while(true)?? Can someone give me a basic example of how I would run this by invoking wait() ? – rob345 Apr 02 '12 at 22:02
  • BTW, debugging this does no good as there it runs fine. It is only after a long period that the program crashes. – rob345 Apr 02 '12 at 22:04
  • Just to clarify when you say that everything "runs fine", you are indicating that no exception is ever thrown and that each of those `close` are successful? – nevets1219 Apr 02 '12 at 22:36
  • i know that no exceptions are thrown. I did not check to see if the close statements are successful. how would I do this? – rob345 Apr 02 '12 at 22:46
  • If it didn't thrown an exception then I would expect that it was successful. As long as you are certain they are called then I'm not sure what else could cause a problem. You could try VisualVM like Handerson suggested or you can try [MAT](http://www.eclipse.org/mat/) which is fairly useful. – nevets1219 Apr 02 '12 at 23:09

4 Answers4

3

I would change this:

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

to this:

                        results = statement.executeQuery(rawQuery);

The latter is certainly the API-approved way to do this, and while I can't say for certain that the former is a problem, it certainly seems like it could create two separate result-sets, of which you only close one.

ruakh
  • 156,364
  • 23
  • 244
  • 282
  • `getResultSet()` returns the current ResultSet so I'm pretty sure that would not create two ResultSet. See http://docs.oracle.com/javase/1.4.2/docs/api/java/sql/Statement.html – nevets1219 Apr 02 '12 at 21:22
  • 1
    @nevets1219: That documentation says "This method should be called only once per result", and its see-also points to `execute`. So I *think* that calling `executeQuery` counts as calling `execute` plus calling `getResultSet`, such that calling `executeQuery` plus calling `getResultSet` would defy the API. – ruakh Apr 02 '12 at 21:26
  • I just tried calling it multiple times and each time the same object was returned - it was also the same object that was returned from `statement.executeQuery(...)`. Perhaps there's some other reason why it shouldn't be called multiple times? I still don't think it results in multiple `ResultSet` being created. – nevets1219 Apr 02 '12 at 21:34
  • 1
    @nevets1219: The reason that it shouldn't be called multiple times is that JDBC drivers are not required to handle that case in any particular way. So unless you have the same JDBC driver as the OP, your investigation is not meaningful. (To be sure: it seems quite likely that you *do* have the same JDBC driver as the OP. But I wouldn't just take that for granted.) – ruakh Apr 02 '12 at 21:38
  • Thanks for that bit, I had forgotten about the JDBC drivers difference. I think I'll ask SO to see why you shouldn't call `getResultSet` more than once, I'm quite curious about that. Do you believe the undefined behavior to be the only reason? – nevets1219 Apr 02 '12 at 21:47
  • Just tried the above code. changing the results command has no effect on the build up of memory. – rob345 Apr 02 '12 at 22:00
2

Unfortunately you don't specify some details about the problem, for example, how big is the result set (# of rows), and how long does it take to run into out of memory exception.

I don't have access right now to the mysql driver you have, but I ran your same code with an H2 database, with 1000 rows in the myTable. The heap size of the JVM was stable during the test, without any memory leak. You can see that in the attached screenshot. The heap size increased a little, then returned to the original position after the GC, up again, down again, on a very stable pattern.

You can run your app and then run the Jvisualvm and connect to your app to see, for example, if the number of results from the database is too large to fit into the existing memory. Which is my guess. In this case the blue line will rapidly go over the max memory.

If that's the case you run your application with -Xmx setting to increase the memory size.

If indeed there is a memory leak it is not in your code, but in the driver you're using. To confirm a memory leak, the blue line in the chart below will go up (allocating memory), the GC will run (freeing up memory) but the blue line never gets back to it's original position leaving behind some objects.

JVisualVM Screenshot

Handerson
  • 375
  • 1
  • 3
0

I would suggest you try two things:

Extend your timer to about 10 seconds. Two is expecting a lot for a slow system.

Put a Thread.currentThread.sleep(10) (or similar) in your idle loop.

I expect you are not waiting for go to complete. While you are spinning on air in your idle loop the database connecion is dying from lack of cycles and every two seconds you add yet another connection and query. No wonder the poor thing is struggling.

OldCurmudgeon
  • 60,862
  • 15
  • 108
  • 197
  • I don't think that's the case. I took out the timer and just put the go() statement in the while(true) loop and I still get an ever growing char[] and byte[]. Curious though, the memory heap seems stable. – rob345 Apr 02 '12 at 22:49
  • i am using the following driver: mysql-connector-java-5.1.11-bin.jar. Could this be the source of the problem? – rob345 Apr 02 '12 at 23:01
  • If `the memory heap seems stable` then you are not leaking. – OldCurmudgeon Apr 03 '12 at 09:34
0

Add the heap dump on out of memory arg and then look at the heap with mat or similar. Using HeapDumpOnOutOfMemoryError parameter for heap dump for JBoss

Community
  • 1
  • 1
davidfrancis
  • 3,393
  • 2
  • 20
  • 19