Сборка мусора сложна, и разные платформы реализуют ее по-разному. Действительно, разные версии одной и той же платформы осуществляют сборку мусора по-разному. (И еще ...)
Типичный современный коллекционер основан на наблюдении, что большинство объектов умирают молодыми; то есть они становятся недоступными вскоре после их создания. Затем куча делится на два или более «пробелов»; например "молодое" пространство и "старое" пространство.
- «Молодое» пространство - это место, где создаются новые объекты, и оно часто собирается. «Молодое» пространство имеет тенденцию быть меньше, и «молодая» коллекция происходит быстро.
- «Старое» пространство - это место, где заканчиваются долгоживущие объекты, и оно собирается нечасто. На «старом» пространстве коллекция, как правило, обходится дороже. (По разным причинам.)
- Объект, переживший несколько циклов GC в «новом» пространстве, получает «владение»; то есть они перемещены в "старое" пространство.
- Иногда мы можем обнаружить, что нам нужно собирать новые и старые пробелы одновременно. Это называется полная коллекция. Полный сборщик мусора является самым дорогим и, как правило, «останавливает мир» на относительно долгое время.
(Есть много других умных и сложных вещей ... которые я не буду вдаваться.)
Ваш вопрос заключается в том, почему использование пространства не значительно снижается, пока вы не позвоните System.gc()
.
Ответ в основном заключается в том, что это эффективный способ сделать что-то.
Настоящая цель сбора не состоит в том, чтобы постоянно освобождать столько памяти. Скорее, цель состоит в том, чтобы обеспечить достаточное количество свободной памяти, когда это необходимо, и сделать это либо с минимальными перегрузками ЦП, либо с минимумом пауз GC.
Таким образом, при нормальной работе ГХ будет вести себя так, как указано выше: делать частые "новые" коллекции пространств и реже "старые" коллекции пространств. И коллекции
будет работать "как требуется".
Но когда вы звоните System.gc()
, JVM , как правило, пытается вернуть как можно больше памяти. Это означает, что он делает "полный gc".
Теперь я думаю, что вы сказали, что для того, чтобы реально изменить ситуацию, требуется пара System.gc()
вызовов, которые могут быть связаны с использованием finalize
методов или Reference
объектов или подобных объектов. Оказывается, что финализуемые объекты и Reference
обрабатываются после того, как основной GC завершен фоновым потоком. На самом деле объекты находятся только в состоянии, когда их можно собирать и удалять после этого. Так что нужен еще один GC, чтобы окончательно избавиться от них.
Наконец, существует проблема общего размера кучи. Большинство виртуальных машин запрашивают память у операционной системы хоста, когда куча слишком мала, но не хотят ее возвращать. Причина в том, что сборщики, как правило, наиболее эффективны, если пропорция мусора к не-мусору высока. Возвращая освобожденную память обратно ОС, JVM повлияет на производительность позже ... если куча снова вырастет.
Я не уверен насчет сборщика Android, но сборщики Oracle отмечают соотношение свободного пространства в конце последовательных "полных" сборок. Они уменьшают общий размер кучи только в том случае, если коэффициент свободного пространства «слишком высок» после заданного числа циклов.
Предполагая, что сборщик Android работает аналогично, это еще одно объяснение того, почему вам пришлось запускать System.gc()
несколько раз, чтобы уменьшить размер кучи.