Java 6 чрезмерное использование памяти - PullRequest
22 голосов
/ 14 декабря 2008

Java 6 потребляет больше памяти, чем вы ожидаете для больших приложений?

У меня есть приложение, которое я разрабатывал в течение многих лет, и до сих пор занимало около 30-40 МБ в моей конкретной тестовой конфигурации; теперь с Java 6u10 и 11 он занимает несколько сотен, пока активен. Он много отскакивает, где-то между 50M и 200M, и когда он простаивает, он делает GC и сбрасывает память прямо вниз. Кроме того, он генерирует миллионы ошибок страниц. Все это наблюдается через диспетчер задач Windows.

Итак, я запустил его под своим профилировщиком (jProfiler) и используя jVisualVM, и оба они указывают на обычное умеренное использование кучи и perm-gen около 30M вместе, даже когда я полностью активен, выполняя мой цикл нагрузочных испытаний. 1007 *

Так что я озадачен! И это не просто запрос дополнительной памяти из пула виртуальной памяти Windows - это отображается как 200M «Использование памяти».

ПОЯСНЕНИЕ: я хочу быть совершенно ясным в этом - наблюдалось в течение 18 часов с Java VisualVM, что куча классов и куча perm gen были совершенно стабильны. Выделенная энергозависимая куча (eden и tenured) остается неподвижной на 16 МБ (которой она достигает в первые несколько минут), и использование этой памяти колеблется в идеальном порядке равномерного роста от 8 МБ до 16 МБ, после чего GC начинает работать сбрасывает его обратно до 8 МБ. В течение этого 18-часового периода система находилась под постоянной максимальной нагрузкой, так как я проводил стресс-тест. Такое поведение идеально и последовательно воспроизводимо, наблюдаемое в многочисленных сериях. Единственная аномалия заключается в том, что при использовании памяти из Windows, наблюдаемой через диспетчер задач, она колеблется от 64 МБ до 900+ МБ.

ОБНОВЛЕНИЕ 2008-12-18: Я запустил программу с -Xms16M -Xmx16M без каких-либо явных негативных последствий - производительность хорошая, общее время работы примерно одинаково. Но использование памяти за короткий промежуток времени все еще достигло максимума на 180M.

Обновление 2009-01-21: Кажется, что ответ может быть в числе тем - см. Мой ответ ниже.


РЕДАКТИРОВАТЬ: И я имею в виду миллионы ошибок страницы буквально - в области 30M +.

РЕДАКТИРОВАТЬ: У меня есть машина 4G, поэтому 200M не имеет значения в этом отношении.

Ответы [ 7 ]

13 голосов
/ 15 декабря 2008

В ответ на обсуждение в комментариях к ответу Рана вот тестовый пример, который доказывает, что JVM будет освобождать память обратно в ОС при определенных обстоятельствах:

public class FreeTest
{
    public static void main(String[] args) throws Exception
    {
        byte[][] blob = new byte[60][1024*1024];
        for(int i=0; i<blob.length; i++)
        {
            Thread.sleep(500);
            System.out.println("freeing block "+i);
            blob[i] = null;
            System.gc();
        }
    }
}

Я вижу, что размер процесса JVM уменьшается, когда число достигает 40, как в Java 1.4, так и в Java 6 (от Sun).

Вы даже можете настроить точное поведение с помощью параметров -XX: MaxHeapFreeRatio и -XX: MinHeapFreeRatio - некоторые параметры на этой странице также могут помочь с ответом на исходный вопрос.

9 голосов
/ 14 декабря 2008

Я не знаю о недостатках страницы. но об огромной памяти, выделенной для Java:

  1. JVM от Sun только выделяет память, никогда не освобождает ее (до смерти JVM) освобождает память только после того, как определенное соотношение между потребностями внутренней памяти и выделенной памятью падает ниже (настраиваемого) значения. JVM начинается с суммы, указанной в -Xms, и может быть увеличена до суммы, указанной в -Xmx. Я не уверен, что по умолчанию. Всякий раз, когда JVM требуется больше памяти (новые объекты / примитивы / массивы), она выделяет весь фрагмент из ОС. Однако, когда потребность исчезает (мгновенная необходимость, см. Также 2), она не освобождает память обратно ОС немедленно , но сохраняет ее до тех пор, пока это соотношение не будет достигнуто. Однажды мне сказали, что JRockit ведет себя лучше, но я не могу это проверить.

  2. JVM от Sun запускает полный сборщик мусора на основе нескольких триггеров. Одним из них является объем доступной памяти - когда она слишком сильно падает, JVM пытается выполнить полный GC, чтобы освободить еще немного. Таким образом, когда из ОС выделяется больше памяти (мгновенная необходимость), вероятность полного GC уменьшается. Это означает, что, хотя вы можете видеть 30 МБ «живых» объектов, может быть гораздо больше «мертвых» объектов (недоступных), просто ожидающих, когда произойдет сборщик мусора. Я знаю, что у твоего комплекта есть великолепный вид, называемый «мертвые объекты», где ты можешь видеть эти «остатки».

  3. В режиме "-server" JVM от Sun запускает GC в параллельном режиме (в отличие от старого серийного GC "Stop the World"). Это означает, что, хотя может быть мусор для сбора, он не может быть собран сразу, потому что другие потоки занимают все доступное время ЦП. Он будет собран до того, как будет исчерпан объем памяти (ну, в общем, смотрите. http://java.sun.com/javase/technologies/hotspot/gc/gc_tuning_6.html),, если из ОС можно выделить больше памяти, это может произойти до запуска GC.

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

edit: изменено «никогда не деактивируется» на «только после достижения соотношения».

7 голосов
/ 22 января 2009

Чрезмерное создание потоков прекрасно объясняет вашу проблему:

  • Каждый поток получает свой собственный стек, который отделен от динамической памяти и поэтому не зарегистрирован профилировщиками
  • Размер стека потока по умолчанию довольно большой, IIRC 256 КБ (по крайней мере, это было для Java 1.3 )
  • Память стека протоколов, вероятно, не используется повторно, поэтому, если вы создадите и уничтожите множество потоков, вы получите множество ошибок на странице

Если вам действительно нужно иметь сотни потоков, размер стека можно настроить с помощью параметра командной строки -Xss.

4 голосов
/ 15 декабря 2008

Сборка мусора - довольно загадочная наука. По мере развития уровня техники ненастроенное поведение будет изменяться в ответ.

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

В этом случае Java 6, по-видимому, считает, что резервирование дополнительного пространства, в которое может врастать куча, даст ему лучшую производительность - предположительно потому, что полагает, что это приведет к гибели большего количества объектов в пространстве Eden и ограничению числа объекты, продвигаемые в заемное поколение поколений. А исходя из характеристик вашего оборудования, JVM не считает, что это дополнительное зарезервированное пространство кучи вызовет какие-либо проблемы. Обратите внимание, что многие (хотя и не все) допущения, которые JVM делает в своем заключении, основаны на «типичных» приложениях, а не на вашем конкретном приложении. Он также делает предположения на основе вашего оборудования и профиля ОС.

Если JVM сделала неверные предположения, вы можете влиять на ее поведение через командную строку, хотя легко ошибиться ...

Информацию об изменениях производительности в Java 6 можно найти здесь .

В документе Управление памятью .

обсуждается вопрос об управлении памятью и влиянии на производительность.
3 голосов
/ 22 января 2009

За последние несколько недель у меня была причина исследовать и исправить проблему с объектом пула потоков (многопоточный пул выполнения до Java 6), где запускалось гораздо больше потоков, чем требовалось. В рассматриваемых работах может быть до 200 ненужных потоков. И нити постоянно умирали и их заменяли новые.

Исправив эту проблему, я подумал снова запустить тест, и теперь кажется, что потребление памяти стабильно (хотя примерно на 20 МБ выше, чем у более старых JVM).

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

Если кто-то захочет поэкспериментировать и ответить на это своими выводами, я приму этот ответ; в противном случае я приму это (после 2-дневного периода ожидания).

Кроме того, частота отказов страниц значительно ниже (в 10 раз).

Кроме того, исправления в пуле потоков исправили некоторые проблемы с конкуренцией.

1 голос
/ 07 сентября 2011

Много ли выделено памяти вне кучи Java после обновления до Java 6u10? Может быть только одно:

Замечания к выпуску Java6 u10 : "Новый конвейер ускоренной визуализации Direct3D (...) включен по умолчанию"

Sun включила ускорения Direct 3D по умолчанию в Java 6u10. Эта опция создает множество (временных?) Собственных буферов памяти, которые размещаются вне кучи Java. Добавьте следующий аргумент vm, чтобы отключить его снова:

-Dsun.java2d.d3d = false

Обратите внимание, что это НЕ отключит аппаратное ускорение 2D, только некоторые функции, которые могут использовать аппаратное ускорение 3D. Вы увидите, что использование Java-кучи увеличится до 7 МБ, но это хороший компромисс, поскольку вы сэкономите ~ 100 МБ (+) этой временной энергозависимой памяти.

Я провел достаточное количество испытаний в настольном приложении 2 Swing на двух платформах:

  • высокопроизводительный Intel-i7 с графической картой nVidia GTX 260,
  • 3-х летний ноутбук с графикой Intel.

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

Если вам нужно выполнить некоторую настройку производительности, вот отличный справочник (например, «Устранение неполадок Java 2D»)

0 голосов
/ 17 декабря 2008

Вы используете коллектор ConcMarkSweep? Это может увеличить объем памяти, необходимый для вашего приложения, из-за повышенной фрагментации памяти и «плавающего мусора» - объектов, которые становятся недоступными только после того, как сборщик их изучил, и поэтому не собираются до следующего прохода.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...