Слишком много открытых файловых дескрипторов - PullRequest
9 голосов
/ 02 ноября 2009

Я работаю над огромным унаследованным Java-приложением со множеством рукописных материалов, которые в настоящее время вы позволяете фреймворку обрабатывать.

Проблема, с которой я сейчас сталкиваюсь, заключается в том, что у нас не хватает файловых дескрипторов на нашем сервере Solaris. Я хотел бы знать, как лучше всего отслеживать дескрипторы открытых файлов? Где искать и что может привести к исчерпанию дескрипторов открытых файлов?

Я не могу отладить приложение под Solaris, только в моей среде разработки Windows. Есть ли смысл анализировать дескрипторы открытых файлов под Windows?

Ответы [ 12 ]

8 голосов
/ 02 ноября 2009

Одна хорошая вещь, которую я нашел для отслеживания незамеченных файловых дескрипторов, - FindBugs:

http://findbugs.sourceforge.net/

Он проверяет многие вещи, но одна из самых полезных - операции открытия / закрытия ресурса. Это программа статического анализа, которая работает с вашим исходным кодом, а также доступна в виде плагина eclipse.

7 голосов
/ 02 ноября 2009

В окнах вы можете посмотреть на дескрипторы открытых файлов с помощью Process Explorer:

http://technet.microsoft.com/en-us/sysinternals/bb896653.aspx

В Solaris вы можете использовать "lsof" для отслеживания дескрипторов открытых файлов

2 голосов
/ 02 ноября 2009

Стоит помнить, что открытые сокеты также используют файловые дескрипторы в системах Unix. Поэтому вполне может быть что-то вроде утечки пула соединений с базой данных (например, открытые соединения с базой данных не закрываются и не возвращаются в пул), что приводит к этой проблеме - конечно, я уже видел эту ошибку раньше, вызванную утечкой пула соединений.

2 голосов
/ 02 ноября 2009

Это может быть непрактично в вашем случае, но то, что я делал однажды, когда у меня была похожая проблема с открытыми соединениями с базой данных, было переопределением функции "open" моей собственной. (Удобно, что у меня уже была эта функция, потому что мы написали свой собственный пул соединений.) Затем в свою функцию я добавил запись в таблицу, записывающую открытие. Я сделал вызов трассировки стека и сохранил идентификатор вызывающего абонента вместе с временем вызова, и я забыл, что еще. Когда соединение было освобождено, я удалил запись в таблице. Затем у меня был экран, на котором мы могли вывести список открытых записей. Затем вы можете посмотреть на отметку времени и легко увидеть, какие соединения были открыты в течение невероятного количества времени и какие функции выполняли эти открытия.

Из этого мы смогли быстро отследить пару функций, которые открывали соединения и не могли их закрыть.

Если у вас много открытых дескрипторов файлов, есть вероятность, что вы не сможете закрыть их, когда закончите где-нибудь. Вы говорите, что проверили правильные блоки try / finally, но я подозреваю, что где-то в коде вы либо пропустили плохой, либо у вас есть функция, которая передает и никогда не добирается до конца. Я полагаю, также возможно, что вы действительно делаете правильные закрытия каждый раз, когда открываете файл, но вы открываете сотни файлов одновременно. Если это так, я не уверен, что вы можете сделать, кроме серьезного редизайна программы, чтобы манипулировать меньшим количеством файлов, или серьезного редизайна программы, чтобы поставить в очередь доступ к вашим файлам. (На этом этапе я добавляю обычное: «Не зная деталей вашего приложения и т. Д.»)

2 голосов
/ 02 ноября 2009

Этот небольшой скрипт помогает мне следить за количеством открытых файлов, когда мне нужно проверить ic count. Если он использовался в Linux, то для Solaris вы должны его исправить (может быть :))

#!/bin/bash
COUNTER=0
HOW_MANY=0
MAX=0
# do not take care about COUNTER - just flag, shown should we continie or not
while [ $COUNTER -lt 10 ]; do
    #run until process with passed pid alive
    if [ -r "/proc/$1" ]; then
        # count, how many files we have
        HOW_MANY=`/usr/sbin/lsof -p $1 | wc -l`
        #output for live monitoring
        echo `date +%H:%M:%S` $HOW_MANY
        # uncomment, if you want to save statistics
        #/usr/sbin/lsof -p $1 > ~/autocount/config_lsof_`echo $HOW_MANY`_`date +%H_%M_%S`.txt

        # look for max value
        if [ $MAX -lt $HOW_MANY ]; then
            let MAX=$HOW_MANY
            echo new max is $MAX
        fi 
        # test every second. if you don`t need so frequenlty test - increase this value
        sleep 1
    else
        echo max count is $MAX
        echo Process was finished
        let COUNTER=11
    fi
done

Также вы можете попробовать поиграть с jvm ontion -Xverify: none - следует отключить проверку jar (если большинство открытых файлов - jars ...). Для утечек через незакрытый FileOutputStream вы можете использовать findbug (описано выше) или попытаться найти статью о том, как исправить стандартную java FileOutputStream / FileInputStream, где вы можете увидеть, кто открывает файлы, и забыл закрыть их. К сожалению, сейчас не могу найти эту статью, но она существует :) Также подумайте об увеличении ограничения файлов - для современных * nix-ядер не проблема обрабатывать более 1024 фд.

2 голосов
/ 02 ноября 2009

Чтобы ответить на вторую часть вопроса:

что может привести к исчерпанию дескрипторов открытых файлов?

Очевидно, много файлов открывается, а затем не закрывается.

Самый простой сценарий состоит в том, что ссылки на любые объекты, которые содержат собственные маркеры (например, FileInputStream), выбрасываются перед закрытием, что означает, что файлы остаются открытыми, пока объекты не будут завершены.

Другой вариант заключается в том, что объекты хранятся где-то и не закрываются. Дамп кучи мог бы сказать вам, что задерживается, где (jmap и jhat включены в JDK, или вы можете использовать jvisualvm если вы хотите графический интерфейс). Вы, вероятно, заинтересованы в поиске объектов, владеющих FileDescriptor s.

1 голос
/ 02 ноября 2009

Я бы дважды проверил настройки среды на вашей коробке Solaris. Я считаю, что по умолчанию Solaris допускает только 256 файловых дескрипторов на процесс. Для серверного приложения, особенно если оно выполняется на выделенном сервере, это очень мало. На рисунке 50 или более дескрипторов для открытия JRE и библиотечных JAR, а затем по крайней мере один дескриптор для каждого входящего запроса и запроса к базе данных, возможно, больше, и вы можете увидеть, как это просто не сократит горчицу серьезный сервер.

Посмотрите на файл /etc/system, для значений rlim_fd_cur и rlim_fd_max, чтобы увидеть, что настроила ваша система. Затем подумайте, разумно ли это (вы можете увидеть, сколько файловых дескрипторов открыто во время работы сервера с помощью команды lsof, в идеале с параметром -p [идентификатор процесса].

1 голос
/ 02 ноября 2009

Не прямой ответ на ваш вопрос, но эти проблемы могут быть результатом неправильного высвобождения файловых ресурсов в вашем устаревшем коде. Например, если вы работаете с классами FileOutputsStream, убедитесь, что методы close вызываются в блоке finally, как в этом примере:

FileOutputsStream out = null;
try {
  //You're file handling code
} catch (IOException e) {
  //Handle
} finally {
  if (out != null) {
    try { out.close(): } catch (IOException e) { }
  }
}
1 голос
/ 02 ноября 2009

Я бы начал с того, что попросил моего системного администратора получить список всех открытых файловых дескрипторов для этого процесса. Разные системы делают это по-разному: в Linux, например, есть каталог /proc/PID/fd. Напоминаю, что у Solaris есть команда (может быть pfiles ?), Которая будет делать то же самое - ваш системный администратор должен это знать.

Однако, если вы не увидите много ссылок на один и тот же файл, список fd вам не поможет. Если это серверный процесс, возможно, по какой-то причине у него много открытых файлов (и сокетов). Единственный способ решить эту проблему - настроить системный лимит для открытых файлов - вы также можете проверить лимит для каждого пользователя с помощью ulimit , но в большинстве текущих установок, который равен системному лимиту.

0 голосов
/ 14 марта 2014

Это шаблон кодирования, который помогает найти незакрытые ресурсы. Закрывает ресурсы, а также жалуется в журнале на проблему.

class
{
    boolean closed = false;
    File file;

    close() {
        closed = true;
        file.close();
    }

    finalize() {
        if (!closed) {
            log error "OI! YOU FORGOT TO CLOSE A FILE!"
        file.close();
    }
}

Оберните вышеупомянутые вызовы file.close () в блоки try-catch, которые игнорируют ошибки.

Кроме того, в Java 7 появилась новая функция «попробуй с ресурсом», которая может автоматически закрывать ресурсы.

...