Куча Java перегружена недостижимыми объектами - программирование
Подтвердить что ты не робот

Куча Java перегружена недостижимыми объектами

У нас возникли серьезные проблемы с нашим приложением Java EE. В частности, приложение запускает до 99% кучи старого поколения в течение нескольких минут после запуска. Никаких OOM не бросают, но эффективно JVM не реагирует. Jstat показывает, что старое поколение вообще не уменьшает размер, сбор мусора продолжается, а kill -3 говорит:

Heap
 PSYoungGen      total 682688K, used 506415K [0xc1840000, 0xf3840000, 0xf3840000)
  eden space 546176K, 92% used [0xc1840000,0xe06cd020,0xe2da0000)
  from space 136512K, 0% used [0xe2da0000,0xe2da0000,0xeb2f0000)
  to   space 136512K, 0% used [0xeb2f0000,0xeb2f0000,0xf3840000)
 PSOldGen        total 1536000K, used 1535999K [0x63c40000, 0xc1840000, 0xc1840000)
  object space 1536000K, 99% used [0x63c40000,0xc183fff8,0xc1840000)

Параметры VM:

-Xmx2300m -Xms2300m -XX:NewSize=800m -XX:MaxNewSize=800m -XX:SurvivorRatio=4 -XX:PermSize=256m -XX:MaxPermSize=256m -XX:+UseParallelGC -XX:ParallelGCThreads=4 

(я изменил его с 2300m heap/1800m new gen, как попытку решить проблему)

Я взял кучу кучи JVM, когда он добрался до состояния "из памяти" (взял навсегда), и на нем запустил Eclipse Memory Analyzer.

Результаты довольно забавные. Около 200 Мб заняты объектами всех видов (некоторые из них принадлежат больше других), но все остальное - 1.9Gb - все недостижимы (может быть, следует отметить, что большинство занято объектами GSON, но я не думаю, что это указание на что-либо, только говорит, что мы отказываемся от множества объектов GSON во время работы сервера).

Любое объяснение того, почему виртуальная машина имеет столько недостижимых объектов и не может собирать их вообще?

JVM:

$ /0/bin/java -version
java version "1.6.0_37"
Java(TM) SE Runtime Environment (build 1.6.0_37-b06)
Java HotSpot(TM) Server VM (build 20.12-b01, mixed mode)

Когда система попадает в этот киоск, вот то, что подробный GC продолжает печатать:

922.485: [GC [1 CMS-initial-mark: 511999K(512000K)] 1952308K(2048000K), 3.9069700 secs] [Times: user=3.91 sys=0.00, real=3.91 secs] 
926.392: [CMS-concurrent-mark-start]
927.401: [Full GC 927.401: [CMS927.779: [CMS-concurrent-mark: 1.215/1.386 secs] [Times: user=5.84 sys=0.13, real=1.38 secs] (concurrent mode failure): 511999K->511999K(512000K), 9.4827600 secs] 2047999K->1957315K(2048000K), [CMS Perm : 115315K->115301K(262144K)], 9.4829860 secs] [Times: user=9.78 sys=0.01, real=9.49 secs] 
937.746: [Full GC 937.746: [CMS: 512000K->511999K(512000K), 8.8891390 secs] 2047999K->1962252K(2048000K), [CMS Perm : 115302K->115302K(262144K)], 8.8893810 secs] [Times: user=8.89 sys=0.01, real=8.89 secs] 

решаемые

Как предложил Пол Беллора, это было вызвано слишком большим количеством объектов, созданных в JVM, за слишком короткий промежуток времени. На данный момент отладка становится довольно утомительной. То, что я закончил, - это инструменты для классов, использующих настраиваемый агент JVM. Инструментарий будет считать вызовы метода и конструктора. Затем подсчеты были проверены. Я обнаружил, что незаметная одиночная операция создаст около 2 миллионов объектов и вызовет некоторые отдельные методы примерно 1,5 миллиона раз (нет, не было циклов). Сама операция была идентифицирована, будучи медленной по сравнению с другими. Вы можете использовать любой профилировщик hotspot (что-то вроде visualVM), но у меня были всевозможные проблемы с ними, поэтому я написал свои собственные.

Я все еще думаю, что поведение JVM является загадкой. Похоже, сборщик мусора приходит в стойло и не очищает больше памяти, но распределитель памяти ожидает его (и, таким образом, не бросается OOM). Вместо этого я ожидал, что он очистит всю эту недостижимую память. Но поведение приложения было бы не намного лучше, так как большинство времени было бы потрачено на сбор мусора в любом случае.

4b9b3361

Ответ 1

Любое объяснение того, почему виртуальная машина имеет столько недостижимых объектов и не может собирать их вообще?

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

Виной может быть, например, какой-либо запрос API, который делается много раз, или же "застревает" в некотором ошибочном состоянии, таком как описанный мной сценарий бесконечной разбивки на страницы. То, что любая ситуация сводится к миллионам объектов gson ответа (которые указывают на String (что указывает на char[] s)), создаются и затем становятся доступными для GC.

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

Ответ 2

Основываясь на вашей статистике, мне сложно поверить, что у вас есть 1.9G недостижимых данных. Он больше похож на достигнутый верхний предел GC.

Рассмотрим

937.746: [Полный GC 937.746: [CMS: 512000K- > 511999K (512000K), 8.8891390 secs] 2047999K- > 1962252K (2048000K), [CMS Perm: 115302K- > 115302K (262144K)], 8,8893810 secs] [Время: пользователь = 8,89 sys = 0.01, real = 8.89 с.]

Если это так, то Full GC выпускает 85K данных. Если у вас есть 1.9G недостижимого кода, вы увидите 2047999 -> ~300000.

Также

object space 1536000K, 99% 

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

Мне нужно будет увидеть больше доказательств того, что у вас есть 1.9G недостижимых данных, кроме того, что просто сказано.