Nightmare java leak… with loop and jdbc

2019-02-27 00:43发布

问题:

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.

回答1:

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.



回答2:

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.



回答3:

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.



回答4:

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