Проблемы с потреблением памяти в Java-программе
У меня есть Java-программа, которая работает на моем компьютере Ubuntu 10.04 и без какого-либо взаимодействия с пользователем неоднократно запрашивает базу данных MySQL, а затем создает img- и txt файлы в соответствии с данными, считываемыми из БД. Он обрабатывает десятки тысяч запросов и создает десятки тысяч файлов.
После нескольких часов работы доступная память на моей машине, включая пространство подкачки, полностью израсходована. Я не запускал другие программы, и процессы, выполняющиеся в фоновом режиме, не потребляют много памяти и не растут в потреблении.
Чтобы узнать, что выделяет столько памяти, я хотел проанализировать кучу кучи, поэтому я начал процесс с -Xms64m -Xmx128m -XX: + HeapDumpOnOutOfMemoryError.
К моему удивлению, ситуация была такой же, как и раньше, через несколько часов программа выделяла весь своп, который выходит за пределы заданного максимума 128 м.
Другой запуск, отлаженный с помощью VisualVM, показал, что распределение кучи никогда не превышает максимальный 128 м - когда выделенная память приближается к максимальному, большая часть его снова освобождается (я предполагаю сборщик мусора).
Таким образом, это не может быть проблемой неуклонно растущей кучи.
Когда вся память испорчена:
бесплатно показывает следующее:
total used free shared buffers cached
Mem: 2060180 2004860 55320 0 848 1042908
-/+ buffers/cache: 961104 1099076
Swap: 3227640 3227640 0
top показывает следующее:
USER VIRT RES SHR COMMAND
[my_id] 504m 171m 4520 java
[my_id] 371m 162m 4368 java
(безусловно, два "самых больших" процесса и только запущенные java-процессы)
Мой первый вопрос:
- Как я могу узнать на уровне ОС (например, с помощью средств командной строки), что выделяет столько памяти? top/htop мне не помог. В случае многих, многие крошечные процессы того же типа едят память: есть ли способ разумно суммировать подобные процессы? (Я знаю, что это, вероятно, вне темы, поскольку это вопрос Linux/Ubuntu, но моя основная проблема все еще может быть связана с Java)
Мои старые вопросы:
- Почему не используется память моей программы в верхнем выпуске?
- Как узнать, что выделяет столько памяти?
- Если куча не является проблемой, является единственным "распределяющим фактором" стека? (
стек не должен быть проблемой, так как нет глубокой "глубины вызова метода" )
- Как насчет внешних ресурсов как соединений DB?
Ответы
Ответ 1
Поскольку после дня я не задал никакой активности, я задал вопрос (до 23 марта), и поскольку я все еще не мог найти причину потребления памяти, я "решил" проблему прагматично.
Программа, вызывающая проблему, в основном повторяет "задачу" (т.е. запрашивает БД и затем создает файлы). Относительно легко параметризовать программу, чтобы выполнялось определенное подмножество задач, а не все из них.
Итак, теперь я многократно запускаю свою программу из оболочки script, в каждом процессе выполняется только набор задач (параметризованных через аргументы). В итоге все задачи выполняются, но поскольку один процесс обрабатывает только подмножество задач, проблем с памятью больше не возникает.
Для меня это достаточное решение. Если у вас есть аналогичная проблема, и ваша программа имеет пакетную структуру выполнения, это может быть прагматичным подходом.
Когда я нахожу время, я рассмотрю новые предложения, которые, надеюсь, идентифицируют основную причину (спасибо за помощь!).
Ответ 2
Если действительно ваш Java-процесс является тем, который принимает память, и нет ничего подозрительного в VisualVM или дампе памяти, тогда он должен быть где-то в собственном коде - либо в JVM, либо в некоторых библиотеках, которые вы используете. На уровне JVM это может быть, например, если вы используете NIO или файлы с отображением памяти. Если в некоторых ваших библиотеках используются собственные вызовы или вы используете драйвер JDBC типа 4 для своей базы данных, тогда может возникнуть утечка.
Некоторые предложения:
- Ниже приведены некоторые сведения о том, как найти утечки памяти в собственном коде здесь. Хорошо читать.
- Как обычно, убедитесь, что вы правильно закрываете все ресурсы (файлы, потоки, подключения, темы и т.д.). Большинство из них вызывают встроенную реализацию в какой-то момент, так что потребляемая память может быть не видна непосредственно в JVM
- Проверьте ресурсы, потребляемые на уровне ОС - количество открытых файлов, файловые дескрипторы, сетевые подключения и т.д.
Ответ 3
@maximdim ответ - большой общий совет для такого рода ситуаций. Что, вероятно, происходит здесь, так это то, что сохраняется очень маленький объект Java, который вызывает зависание некоторого большего объема встроенной (OS-level) памяти. Эта нативная память не учитывается в куче Java. Объект Java, вероятно, настолько мал, что вы достигнете предела вашей системной памяти задолго до того, как удержание Java-объекта будет перегружать кучу.
Таким образом, трюк для поиска заключается в том, чтобы использовать последовательные кучи кучи, достаточно далеко друг от друга, что вы заметили рост памяти для всего процесса, но не так далеко друг от друга, что тонна работы продолжалась. То, что вы ищете, - это подсчет объектов Java в куче, которые продолжают увеличиваться и иметь встроенную память.
Это могут быть файловые дескрипторы, сокеты, соединения db или дескрипторы изображений, чтобы назвать несколько, которые могут быть вам применимы.
В более редких случаях существует собственный ресурс, который протекает через реализацию java, даже после того, как объект Java собирает мусор. Однажды я столкнулся с ошибкой WinCE 5, где 4k просочились с каждым разъемом. Таким образом, не было роста объектов Java, но был рост использования памяти процесса. В этих случаях полезно сделать некоторые счетчики и отслеживать распределение Java-объектов с собственной памятью по сравнению с фактическим ростом. Затем в достаточно коротком окне вы можете искать любые корреляции и использовать их для создания небольших тестовых ящиков.
Еще один намек, убедитесь, что все ваши закрытые операции находятся в конце блоков, на всякий случай исключение выталкивает вас из вашего нормального потока управления. Известно, что это также вызывает такую проблему.
Ответ 4
Хмм... используйте ipcs, чтобы проверить, что сегменты разделяемой памяти не остаются открытыми. Проверьте дескрипторы открытых файлов вашего JVM (/proc/<jvm proccess id>/fd/*
). В верхней части введите fpFp
, чтобы показать swap и sort, используя swap список задач.
То, что я могу придумать сейчас, надеюсь, что это поможет хотя бы немного.
Ответ 5
Как отмечают @maximdim и @JamesBranigan, вероятным виновником является некоторое нативное взаимодействие с вашим кодом. Но поскольку вы не смогли точно определить, где проблемное взаимодействие использует доступные инструменты, почему бы вам не попробовать подход грубой силы?
Вы выделили процесс из двух частей: запросите MySQL и напишите файлы. Либо одна из этих вещей может быть исключена из процесса в качестве теста. Проверьте один: устраните запрос и жесткий код содержимого, которое было бы возвращено. Протестируйте два: выполните запрос, но не документируйте файлы. У вас все еще есть утечки?
Могут быть и другие проверяемые случаи, в зависимости от того, что делает ваше приложение.
Ответ 6
Создаете ли вы отдельные потоки для выполнения своих "задач"? Память, используемая для создания потоков, отделена от кучи Java.
Это означает, что даже если вы укажете -Xmx128m
, память, используемая процессом Java, может быть намного выше, в зависимости от того, сколько потоков вы используете и размер стека потоков (каждый поток получает выделенный стек, указанного размера на -Xss
).
Пример из работы в последнее время:
У нас была куча Java 4 ГБ (-Xmx4G
), но процесс ОС потреблял до 6 ГБ,
также используя пространство подкачки.
Когда я проверил статус процесса с помощью cat /proc/<PID>/status
, я заметил, что у нас было 11000 потоков.
Поскольку мы установили -Xss256K
, это легко объяснить: 10000 потоков - 2,5 ГБ.
Ответ 7
Кэширование файловой системы, вероятно, вызывает это, кэш файловой системы будет потреблять всю доступную память при выполнении большого количества ввода-вывода. Такое поведение системы не должно отрицательно сказываться на явлении, ядро немедленно освободит кеш файловой системы, когда память запрошена процессом.
Ответ 8
Вы говорите, что создаете файлы изображений, создаете ли вы объекты изображений? Если да, вызываете ли вы dispose() на этих объектах, когда закончите?
Если я правильно помню, java не предвидит, что объекты выделяют собственные ресурсы, которые должны быть явно размещены.