Кошмарная утечка Java ... с циклом и JDBC - PullRequest
2 голосов
/ 03 апреля 2012

Когда я запускаю следующий код в профилировщике, я получаю 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 () завершается?Каждый раз, когда я запускаю сборщик мусора в профилировщике, я получаю другое выживающее поколение.Спасибо.

Ответы [ 4 ]

3 голосов
/ 03 апреля 2012

Я бы изменил это:

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

к этому:

                        results = statement.executeQuery(rawQuery);

Последний, безусловно, является одобренным API способом сделать это, и хотя я не могу с уверенностью сказать, что первый является проблемой, он, безусловно, кажется , как будто он может создать два отдельных набора результатов. из которых вы закрываете только одну.

2 голосов
/ 03 апреля 2012

К сожалению, вы не указываете некоторые подробности о проблеме, например, насколько велик набор результатов (количество строк) и сколько времени требуется, чтобы столкнуться с нехваткой памяти.

У меня нет сейчас доступа к драйверу mysql, который у вас есть, но я запустил ваш тот же код с базой данных H2, с 1000 строками в myTable. Размер кучи JVM был стабильным во время теста, без какой-либо утечки памяти. Вы можете увидеть это на прикрепленном скриншоте. Размер кучи немного увеличился, затем вернулся к исходному положению после GC, снова вверх, вниз снова, на очень стабильной схеме.

Вы можете запустить свое приложение, а затем запустить Jvisualvm и подключиться к нему, чтобы посмотреть, например, слишком ли велико количество результатов из базы данных, чтобы поместиться в существующую память. Это мое предположение. В этом случае синяя линия будет быстро превышать максимальную память.

В этом случае ваше приложение запускается с параметром -Xmx для увеличения объема памяти.

Если действительно есть утечка памяти, то это не в вашем коде, а в используемом вами драйвере. Чтобы подтвердить утечку памяти, синяя линия на приведенной ниже диаграмме будет увеличиваться (выделение памяти), GC будет работать (освобождая память), но синяя линия никогда не вернется в исходное положение, оставляя позади некоторые объекты.

JVisualVM Screenshot

0 голосов
/ 03 апреля 2012

Добавьте дамп кучи вне аргумента памяти, а затем посмотрите на кучу с помощью mat или аналогичного. Использование параметра HeapDumpOnOutOfMemoryError для дампа кучи для JBoss

0 голосов
/ 03 апреля 2012

Я бы посоветовал вам попробовать две вещи:

Увеличьте таймер примерно до 10 секунд.Два ждет много для медленной системы.

Поместите Thread.currentThread.sleep(10) (или аналогичный) в ваш цикл ожидания.

Я ожидаю, что вы не ожидаете завершения go.Пока вы вращаетесь в эфире в режиме ожидания, соединение с базой данных умирает из-за отсутствия циклов, и каждые две секунды вы добавляете еще одно соединение и запрос.Неудивительно, что бедняга борется.

...