Як звільнити пам'ять на Java?


146

Чи існує спосіб звільнення пам'яті на Java, схожий на free()функцію C ? Або встановлення об’єкта на нуль і покладання на GC єдиний варіант?


151
Добре ... давайте розберемо одне. Тільки тому, що ти вважаєш, що щось є поганою практикою, а не те, що спонукаєш робити, не робить це гідним голосування. Це чітке і справедливе питання, задаючи питання про те, чи є спосіб звільнити пам'ять на Java, не покладаючись на збирання сміття. Незважаючи на те, що це може бути відмовлено і, як правило, не корисно або хороша ідея, ви не можете знати, що не існує сценаріїв, де це може знадобитися, не знаючи, що знає Фелікс. Фелікс, можливо, навіть не планує його використовувати. Він може просто захотіти знати, чи це можливо. Це, в жодному разі, не заслуговує зниження голосу.
Даніель Бінгем

7
Для уточнення, це спрямовано на того, хто проголосував за це - не попередні коментарі обов'язково.
Даніель Бінгем

Відповіді:


96

Java використовує керовану пам'ять, тому єдиний спосіб розподілити пам'ять - це за допомогою newоператора, і єдиний спосіб розподілити пам'ять - це покладатися на смітник.

Цей документ про управління пам'яттю (PDF) може допомогти пояснити, що відбувається.

Ви також можете зателефонувати, System.gc()щоб запропонувати негайно запустити сміттєзбірник. Однак, Java Runtime приймає остаточне рішення, а не ваш код.

Згідно з документацією на Java ,

Виклик методу gc передбачає, що віртуальна машина Java витрачає зусилля на переробку невикористаних об'єктів, щоб зробити пам'ять, яку вони зараз займають, доступною для швидкого використання. Коли управління повертається з виклику методу, віртуальна машина Java доклала максимум зусиль, щоб повернути простір з усіх викинутих об'єктів.


5
Це змушує збирач сміття працювати. Це не змушує звільнити пам'ять, але ...
Пабло Санта-Крус,

13
Ні Пабло, це не примушує GC бігати.
Джеспер

1
Мені сказала дуже надійна людина, що всі сміттєзбірники HotSpotVM System.gc()повністю ігнорують .
Есько

1
На winXp java SE GC запускає кожен System.gc () або майже кожен, але API API не гарантує цього.
teodozjan

2
@Pablo Santa Cruz Що ти означає, що це не звільняє пам'ять? Я щойно перевірив це на своїй програмі, яка, здавалося, має витік, і використання оперативної пам'яті, здавалося, стабілізується? А Даніель щойно сказав, що це лише підказує, як тоді відсоток використаних баранів завжди стабілізується кожного разу, коли я називав цей метод. Ви люди мене бентежите.

65

Здається, ніхто не згадував прямо встановлення посилань на об'єкти null , що є законною методикою "звільнення" пам'яті, яку ви можете розглянути.

Наприклад, скажіть, що ви оголосили, що ви List<String>на початку використовували метод, який збільшився в дуже великому розмірі, але його потрібно було лише на півдорозі. Ви можете в цей момент встановити посилання на Списокnull щоб дозволити сміттєзбірнику повернути цей об'єкт до завершення методу (і посилання все одно не виходить із сфери застосування).

Зауважте, що я дуже рідко використовую цю техніку в реальності, але це варто враховувати, коли ми маємо справу з дуже великими структурами даних.


8
Якщо ви дійсно виконуєте багато робіт над об'єктом, який використовується лише для частини методу, я пропоную також; ваш метод занадто складний, розбийте його на частини до та після, або використовуйте блок для першої половини коду (пізніше корисніше для тестових сценаріїв)
Пітер Лоурі,

5
Місце, де встановлення посилання на об'єкт є нульовим, є важливим, коли воно посилається на інший довгоживучий об'єкт (або, можливо, із статичного var). Наприклад, якщо у вас є тривалий масив великих об'єктів, і ви перестаєте використовувати один з цих об'єктів, вам слід встановити посилання масиву на null, щоб зробити об’єкт доступним для GC.
Гарячі лизання

22
System.gc(); 

Працює сміттєзбірник.

Виклик методу gc передбачає, що віртуальна машина Java витрачає зусилля на переробку невикористаних об'єктів, щоб зробити пам'ять, яку вони зараз займають, доступною для швидкого використання. Коли управління повертається з виклику методу, віртуальна машина Java доклала максимум зусиль, щоб повернути простір з усіх викинутих об'єктів.

Не рекомендовано.

Редагувати: Оригінальну відповідь я написав у 2009 році. Зараз це 2015 рік.

Сміттєзбірники ставали все краще в останні 20 років. У цей момент, якщо ви вручну викликаєте сміттєзбірник, можливо, ви захочете розглянути інші підходи:

  • Якщо ви змушуєте GC на обмеженій кількості машин, можливо, варто відсунути точку балансування навантаження від поточної машині, чекаючи її завершення служить для підключених клієнтів, тайм - аут після деякого періоду для підвішування з'єднань, а потім просто важко - відновити СВМ. Це жахливе рішення, але якщо ви дивитесь на System.gc (), примусовий перезапуск може бути можливим зупинкою.
  • Подумайте про використання іншого сміттєзбірника. Наприклад, колектор G1 (новий за останні шість років) - це модель з низькою паузою; він використовує більше процесора в цілому, але найкраще ніколи не змушувати робити жорстку зупинку при виконанні. Оскільки серверні процесори зараз майже всі мають декілька ядер, це дійсно хороший компроміс.
  • Подивіться, як ваші прапори налаштовують використання пам'яті. Особливо в нових версіях Java, якщо у вас немає такої кількості довгострокових об’єктів, подумайте про набуття розміру newgen у купі. newgen (молодий) - це місце, де виділяються нові об’єкти. Для веб-сервера тут розміщується все, створене для запиту, і якщо цього простору буде замало, Java витратить додатковий час на модернізацію об'єктів до довготривалої пам'яті, де їх дорожче вбити. (Якщо newgen трохи завеликий, ви заплатите за це.) Наприклад, у G1:
    • XX: G1NewSizePercent (за замовчуванням до 5; мабуть, це не має значення.)
    • XX: G1MaxNewSizePercent (за замовчуванням до 60; можливо, це підвищить.)
  • Подумайте про те, що з довшею паузою сміливо збираєте сміттєзбірника. Це спричинить більш часті запуски GC, щоб система могла зберігати решту своїх обмежень. У G1:
    • XX: MaxGCPauseMillis (за замовчуванням до 200.)

1
Коментуючи свій власний пост, це часто нічого не робить, а його повторний дзвінок може призвести до того, що JVM стане нестабільним і нічого такого. Він також може перебігти вашу собаку; підходити обережно.
Дін J

1
Я б поставив великий наголос на частині "пропонує" "Виклик методу gc говорить про те, що JVM розширить зусилля"
matt b

2
@Jesper, у відповіді Діна зазначається "пропонує". Насправді він опублікував точну документацію з javadocs методу ...
matt

2
@Software Monkey: Так, я міг би лише відредагувати це. Але оскільки Дін Дж, очевидно, був активним (розміщував лише кілька хвилин тому), я подумав, що люб’язно попросити його зробити це. Якби його не було, я б повернувся сюди і змінив та видалив свій коментар.
Даніель Приден

1
Також варто сказати ЧОМУ це не рекомендується. Якщо JVM зверне увагу на "пропозицію" запустити GC, це майже напевно змусить ваш додаток працювати повільніше, можливо, на багато порядків!
Стівен C

11

* "Я особисто покладаюся на нульові змінні як заповнювач для майбутнього належного видалення. Наприклад, я беру час, щоб звести нанівець всі елементи масиву, перш ніж насправді видалити (зробити нуль) самого масиву."

Це зайве. Спосіб роботи Java GC полягає в тому, що він знаходить об'єкти, на які немає посилання на них, тому якщо у мене є Object x із посиланням (= змінною) a, який вказує на нього, GC не видалить його, оскільки є посилання до цього об'єкта:

a -> x

Якщо ви маєте нульове значення a, це відбувається:

a -> null
     x

Отже, x не має посилання на нього і буде видалено. Те ж саме відбувається, коли ви встановлюєте посилання на інший об'єкт, ніж x.

Отже, якщо у вас є масив масиву, який посилається на об'єкти x, y і z, і змінна a, що посилається на масив, виглядає так:

a -> arr -> x
         -> y
         -> z

Якщо ви маєте нульове значення a, це відбувається:

a -> null
     arr -> x
         -> y
         -> z

Таким чином, GC знаходить arr як відсутність посилань на нього та видаляє, що дає вам цю структуру:

a -> null
     x
     y
     z

Тепер GC знаходить x, y і z і видаляє їх також. Зміна кожної посилання в масиві не покращить нічого, воно просто використовуватиме час та простір CPU у коді (що сказало, це не зашкодить далі. GC все одно зможе виконати так, як слід ).


5

Вагомою причиною бажання звільнити пам'ять від будь-якої програми (java чи ні) є надання більшої кількості пам'яті для інших програм на рівні операційної системи. Якщо у моїй програмі Java використовується 250 МБ, я, можливо, захочу примусити її до 1 МБ та зробити доступним 249 МБ для інших програм.


Якщо вам потрібно явно звільнити шматок 249 Мб, в програмі Java управління пам’яттю не буде першим, над чим я хочу працювати.
Marc DiMillo

3
Але звільнення пам’яті у вашій купі Java не (у загальному випадку) не забезпечує доступ до інших програм.
Гарячий лизає

5

Щоб продовжити відповідь та коментар Yiannis Xanthopoulos та Hot Licks (вибачте, я поки не можу прокоментувати!), Ви можете встановити такі параметри VM, як цей приклад:

-XX:+UseG1GC -XX:MinHeapFreeRatio=15 -XX:MaxHeapFreeRatio=30

У моєму jdk 7 це дозволить випустити невикористану пам'ять VM, якщо більше 30% купи стане вільним після GC, коли VM не працює. Можливо, вам доведеться налаштувати ці параметри.

Хоча я не бачив цього підкресленим у посиланні нижче, зауважте, що деякі сміттєзбірники можуть не підкорятися цим параметрам, і за замовчуванням Java може вибрати один із них, якщо у вас є більше одного ядра (звідси аргумент UseG1GC вище ).

Аргументи В.М.

Оновлення: для java 1.8.0_73 Я бачив, що JVM періодично випускає невеликі суми з налаштуваннями за замовчуванням. Здається, це робити лише в тому випадку, коли ~ 70% купи не використовується, не знаю, чи було б більш агресивним вивільненням, якби ОС мала на фізичній пам'яті.


4

Я зробив експерименти над цим.

Це правда, що System.gc();лише пропонує запустити смітник.

Але виклик System.gc();після встановлення всіх посилань на nullполіпшить продуктивність та зайнятість пам’яті.


Я думаю, ви не можете точно сказати, що "виклик System.gc (); після встановлення всіх посилань на null, покращить продуктивність та зайнятість пам'яті." Тому що існує величезна складність обчислювальної системи System.gc (). І навіть після виклику System.gc () та дійсно збирання сміття, jvm може не повернути пам'ять до ОС або System. JVM може зберегти пам'ять для подальшого використання. Дивіться це відповідь .
Пані Абу Нафей Ібна Захід

3

Якщо ви дійсно хочете виділити та звільнити блок пам’яті, ви можете зробити це за допомогою прямих ByteBuffers. Існує навіть непортативний спосіб звільнити пам'ять.

Однак, як було запропоновано, саме те, що вам потрібно звільнити пам'ять на C, не означає, що це не годиться.

Якщо ви вважаєте, що у вас є справжній випадок корисного використання безкоштовно (), будь ласка, включіть його у запитання, щоб ми побачили, що ви ртуєтеся робити, цілком ймовірно, що є кращий спосіб.


3

Цілком із javacoffeebreak.com/faq/faq0012.html

Нитка з низьким пріоритетом автоматично бере на себе збирання сміття для користувача. Під час простою може зателефонувати потік, і він може почати звільняти пам'ять, попередньо виділену об'єкту на Java. Але не хвилюйтесь - це не видалить ваші об’єкти!

Коли немає ніяких посилань на об'єкт, це стає справедливою грою для сміттєзбірника. Замість того, щоб викликати якусь рутину (наприклад, безкоштовно в C ++), ви просто призначите всі посилання на об’єкт нульовими або призначите новий клас посиланню.

Приклад:

public static void main(String args[])
{
  // Instantiate a large memory using class
  MyLargeMemoryUsingClass myClass = new MyLargeMemoryUsingClass(8192);

  // Do some work
  for ( .............. )
  {
      // Do some processing on myClass
  }

  // Clear reference to myClass
  myClass = null;

  // Continue processing, safe in the knowledge
  // that the garbage collector will reclaim myClass
}

Якщо ваш код збирається запросити великий об'єм пам'яті, ви можете попросити сміттєзбірник почати відбирати місце, а не дозволяти йому робити це як нитка з низьким пріоритетом. Для цього додайте у свій код наступне

System.gc();

Збирач сміття спробує повернути вільний простір, і ваш додаток може продовжувати виконувати з якомога більшою кількістю пам'яті (проблеми з фрагментацією пам'яті можуть застосовуватися на певних платформах).


1

У моєму випадку, оскільки мій код Java передбачається перенести на інші мови найближчим часом (головним чином на C ++), я, принаймні, хочу платити послугу для вивільнення пам'яті належним чином, щоб це допомагало згодом процес перенесення.

Я особисто покладаюся на нульові змінні як заповнювач для майбутнього належного видалення. Наприклад, я потребую часу, щоб звести нанівець всі елементи масиву, перш ніж насправді видалити (зробити нуль) самого масиву.

Але мій випадок дуже особливий, і я знаю, що роблю хіти, коли я роблю це.


1

* "Наприклад, скажіть, що ви оголосили" Список "на початку методу, який збільшився в розмірі, щоб бути дуже великим, але його потрібно було лише до початку шляху. Ви можете в цей момент встановити посилання списку на null щоб дозволити колектору сміття потенційно повернути цей об’єкт до завершення методу (і посилання все одно виходить із сфери застосування). " *

Це правильно, але це рішення може бути не узагальненим. Під час встановлення посилання об'єкта List на null -will- зробить пам'ять доступною для збору сміття, це справедливо лише для об'єкта List примітивних типів. Якщо натомість об'єкт List містить типи посилань, встановлення об'єкта List = null не буде перенаправляти -any- з посилальних типів, що містяться -in- списку. У цьому випадку встановлення об'єкта List = null осиротить містять посилання типи, об’єкти яких не будуть доступні для вивезення сміття, якщо алгоритм збору сміття не є достатньо розумним, щоб визначити, що об'єкти осиротіли.


1
Це насправді не так. Збірник сміття Java досить розумний, щоб правильно впоратися з цим. Якщо ви скасуєте цей список (а об'єкти в Списку не мають інших посилань на них), GC може повернути всі об'єкти в Списку. Він може вирішити не робити цього в даний час, але це відшкодує їх у підсумку. Те саме стосується циклічних посилань. В основному, спосіб роботи GC полягає в тому, щоб чітко шукати осиротілі об'єкти, а потім повертати їх. Це вся робота GC. Те, як ви описуєте це, зробило б GC абсолютно марним.
Даккарон

1

Через Java передбачено автоматичне збирання сміття; іноді вам потрібно буде знати, наскільки великий об’єкт і скільки на нього залишилося. Безкоштовна пам'ять, використовуючи програмно, import java.lang;і Runtime r=Runtime.getRuntime(); отримувати значення пам'яті, використовуючи метод mem1=r.freeMemory();виклику пам'яті r.gc();та викликfreeMemory()


1

Рекомендація від JAVA - присвоїти null

Від https://docs.oracle.com/cd/E19159-01/819-3681/abebi/index.html

Явне призначення нульового значення змінним, які більше не потрібні, допомагає сміттєзбірнику визначити частини пам'яті, які можуть бути безпечно відшкодовані. Хоча Java забезпечує управління пам'яттю, це не запобігає витоку пам'яті або використанню надмірної кількості пам'яті.

Додаток може викликати протікання пам'яті, не випускаючи посилання на об'єкт. Це запобігає поверненню Java-сміттєзбірника цих об'єктів і призводить до збільшення кількості пам'яті. Явно зводить нанівець посилання на змінні після їх використання дозволяє сміттєзбірнику відновлювати пам'ять.

Один із способів виявити витоки пам’яті - використовувати інструменти профілювання та робити знімки пам’яті після кожної транзакції. Додаток без витоку в стаціонарному стані покаже стійку активну пам’ять після збирання сміття.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.