Як змусити збирання сміття на Яві?


225

Чи можна змусити збирати сміття на Java, навіть якщо це складно зробити? Я знаю , про System.gc();та , Runtime.gc();але вони тільки пропонують зробити GC. Як я можу примусити GC?


30
Можливо, було б корисно надати певні підґрунтя для того, чому потрібно змусити GC. Зазвичай у мові, зібраній зі сміттям, погана практика явно викликати колекціонера.
Джастін Етьє

3
Даний JVM може забезпечити кілька методів вивезення сміття, кожен з яких має свої переваги та недоліки, і часто дану ситуацію можна уникнути, просто натякнувши JVM під час запуску. Будь ласка, детально розробимо сценарій.
Thorbjørn Ravn Andersen

3
jmap -histo: жива <PID> stackoverflow.com/questions/6418089 / ...

5
Ось випадок використання для примусового збору сміття: у мене є сервер з купою 30 Гб, з яких зазвичай використовується ~ 12 ГБ (~ 5 М об'єктів). Кожні 5 хвилин сервер витрачає приблизно одну хвилину, виконуючи складне завдання, в якому використовується приблизно 35М додаткових об'єктів. Повний ГК спрацьовує пару разів на годину, незмінно під час складного завдання, і заморожує ВМ на 10 - 15 секунд. Мені б хотілося змусити повний GC працювати в той момент, коли складне завдання не працює; Тоді було б жонглювати 5М живими об'єктами, а не 40м.
Стів

3
@JustinEthier Є один досить очевидний випадок, коли ви, можливо, захочете примусити GC - це тестування будь-якої поведінки, що включає ієрархію типів java.lang.ref.Reference.
Ілля Василенко

Відповіді:


168

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


28
Там повинно бути. non-deterministic == trouble
Pacerier

7
Збір сміття може бути не детермінованим і все ж пропонує спосіб примусити негайне збирання. Наприклад, зазвичай .NET колектор не є детермінованим, але виклик GC.Collect () примушує його запускати. Просто Java вирішує не піддавати цій функції.
Петро Худечек

2
На мій досвід, цей метод завжди викликає сміттєзбірник. Це робиться з достатньою регулярністю, що мої графіки використання пам'яті та кількості оголошених об'єктів завжди суворо лінійні (облік прокладки тощо).
Джим Піварський

Я думав, що виділяючи нові об’єкти, а потім більше не посилаючись на них, сміттєзбірник автоматично запуститься
Bionix1441

@ PetrHudeček У реальному застосуванні .NET GC.Collect()не збирає. У Java gc()це робить.
ajeh

53

Бібліотека jlibs має хороший клас корисності для збору сміття . Ви можете примусити збирання сміття, використовуючи чудовий фокус із об’єктами WeakReference .

RuntimeUtil.gc () з програми jlibs:

   /**
    * This method guarantees that garbage collection is
    * done unlike <code>{@link System#gc()}</code>
    */
   public static void gc() {
     Object obj = new Object();
     WeakReference ref = new WeakReference<Object>(obj);
     obj = null;
     while(ref.get() != null) {
       System.gc();
     }
   }

1
Цей код порушений, тому що слабкий коефіцієнт очищення виходить, як тільки його референт стає слабодоступним, що відбувається до того, як він очиститься від пам'яті.
Марко Топольник

1
Можливо, ви пов'язуєте значення "GC run" із "спогадом відновлено". Об'єкт живе і ще не доопрацьований, але ви не можете більше отримати доступ до нього через слабку посилання. Дещо кращим способом буде використання a PhantomReferenceз, ReferenceQueueа потім ви отримаєте сповіщення після завершення, але все ще перед очищенням. Нарешті, навіть якщо ви успішно виявили, що пам'ять для цього об’єкта була відтворена, це все одно буде означати дуже мало в таких поколіннях GC, як HotSpot. Зазвичай це збігалося б з очищенням молодого покоління.
Марко Топольник

20
ОП просила, і ви заявляли, що надаєте рішення щодо "примусового вивезення сміття". Запуск підсистеми GC - одне, насправді збирання сміття - інше. Наведений вами зразок коду має чіткий намір гарантувати збирання сміття. У всякому разі, це дуже старе питання, явно не про бажання ОП, а про корисність для широкої громадськості. Ніхто не зацікавлений у тому, щоб "змусити підсистему GC працювати" самостійно, не збираючи сміття. Насправді люди зазвичай хочуть гарантувати, що все сміття було зібрано.
Марко Тополник

4
Ви, напевно, не порівнювали це з ефективністю System.gc(); System.gc();, але, безумовно, було б цікаво дізнатися, чи працювало це колись краще. Насправді, достатньо System.gc()було б просто роздрукувати скільки разів дзвонило. Шанс коли-небудь досягти 2 досить невеликий.
Марко Тополник

3
@MarkoTopolnik: "Ніхто не зацікавлений у тому, щоб" змусити підсистему GC працювати "самостійно, не збираючи сміття" .... Насправді мене сьогодні цікавила саме така поведінка. Я вдячний, що ця відповідь була присутня. Моєю метою було перевірити обертальну поведінку обробки журналу GC та отримати формат виводу GC. Ця маленька хитрість допомогла мені швидко заповнити журнали GC.
erik.weathers

49

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

Примітка: це НЕ проста відповідь.


40
+1 за люльс. ніщо не засмучує налагодження краще, ніж хтось із почуттям гумору. крім реально корисної відповіді, тобто.
jsh


25

ТАК майже неможливо змусити вас звертатися до методів у тому самому порядку, і при цьому ці:

System.gc ();
System.runFinalization ();

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

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

ПРИМІТКА: просто майте на увазі, це спрацює лише в тому випадку, якщо в методі доопрацювання не буде перепризначення об'єкта, якщо це станеться, об'єкт збережеться живим, він матиме воскресіння, яке технічно можливо.


10
НІ , навіть ці дві команди НЕ будуть змушувати збирання сміття. Як уже згадували інші, gc()це лише натяк на запуск сміття. runFinalizers()запускає фіналізатори лише на об'єктах, "які було виявлено відкинутими". Якби gc насправді не працював, таких об’єктів може не бути ...
Steffen Heil

також System.runFinalization () не є гарантією того, що щось буде працювати; можливо, що взагалі нічого не відбудеться Це пропозиція - від Javadoc: « Виклик цього методу передбачає , що віртуальна машина Java витрачають зусилля в напрямку запуску методів ФІНАЛІЗОВ об'єктів , які були знайдені , щоб бути відкинуто , але чиї методи фіналізації ще не було запущено »
Каано

21

Згідно з документацією на OutOfMemoryError, він заявляє, що його не буде викинуто, якщо VM не зможе відновити пам'ять після повного збору сміття. Тож якщо ви будете продовжувати розподіляти пам’ять, поки не отримаєте помилку, ви вже змусили повноцінно збирати сміття.

Імовірно, питання, яке ви насправді хотіли задати, було "як я можу повернути пам'ять, на яку я думаю, що мені слід відшкодувати сміття?"


18

Щоб вручну подати запит на GC (не з System.gc ()):

  1. Перейдіть до: папка bin у JDK, наприклад.-C: \ Program Files \ Java \ jdk1.6.0_31 \ bin
  2. Відкрийте jconsole.exe
  3. Підключіться до потрібного локального процесу.
  4. Перейдіть на вкладку «Пам'ять» і натисніть «Виконати GC».

3
Опссс вводить в оману. Наведіть курсор миші на кнопку "Виконати GC". Ви можете попросити JVM виконати GC, але ніколи не примушуйте.
Кумаран

@PinkeshSharma, це не змушує . Це просто прохання, яке, ймовірно, можна повністю ігнорувати.
Pacerier

@Pacerier В ідеальному світі так ... але якщо ти це зробиш, то побачиш, що пам’ять миттєво збільшується ...
Pinkesh Sharma

11

.gc є кандидатом на усунення у майбутніх випусках - інженер Sun Sun одного разу прокоментував, що, можливо, менше ніж двадцять людей у ​​світі насправді знають, як користуватися .gc () - я вчора ввечері кілька годин працював на центральній / критичній Структура даних за допомогою даних, згенерованих SecureRandom, десь близько 40 000 об'єктів vm сповільниться, як би у нього не було покажчиків. Зрозуміло, що він задушився на 16-бітних таблицях вказівників і демонстрував класичну поведінку "несправного обладнання".

Я спробував -Xms і так далі, продовжував трохи посміхатися, поки він не запустився до 57, xxx щось. Тоді він би запустив gc, переходячи від скажімо 57,127 до 57,128 після gc () - приблизно з темпом кодування в таборі Easy Money.

Ваш дизайн потребує фундаментальної переробки, можливо, підходу з розсувним вікном.


1
У мене є щось подібне, багато об’єктів у пам'яті, я не можу їх розмістити. Виняток OutOfMemory кинуто, я хочу змусити GC перевірити, чи є якийсь процес створення безмежного об'єкта чи ці об'єкти використовуються моєю системою.

Здається, ви працюєте над тією самою проблемою, що і я, будь ласка, поясніть: "Нескінченне створення об'єкта" ... хороший дослідницький проект, можливо, ви можете тут поставити запитання чи щось у цій галузі (я тут сортую нове і не знаю "Кінцеву автоматику" про те, як працює сайт) Я вчора спробував і в кінцевому підсумку робив file.dat, коли компілятор поскаржився на "занадто багато коду" на 40 000 баз36. тут і припускаю, що весь JVM обмежений 16-бітовими покажчиками, я думаю, що нам потрібно зробити це агресивно нульовим чином і прочитати з диска ...
Nicholas Jordan

Дійсно, я не розумію тебе. Але щоб бути зрозумілим щодо "Нескінченного створення об'єктів", я мав на увазі, що в моїй великій системі є якийсь фрагмент коду, який створює об'єкти, якими обробляє і живе в пам'яті, я не міг отримати цей фрагмент коду насправді, просто жестом !!

5
Дурниці! Є один очевидний випадок, коли його слід використовувати: тестування коду, який використовує слабкі посилання, щоб ми могли переконатися, що поведінка правильна, коли слабкі посилання очищаються.
Ілля Василенко


6

Специфікація JVM не говорить нічого конкретного щодо збору сміття. Завдяки цьому, продавці вільні впроваджувати GC на своєму шляху.

Тож ця розпливчастість викликає невизначеність у поведінці збору сміття. Ви повинні перевірити свої дані JVM, щоб знати про підходи / алгоритми збору сміття. Також є варіанти налаштування поведінки.


4

Якщо вам потрібно примусити збирання сміття, можливо, ви повинні розглянути, як ви керуєте ресурсами. Ви створюєте великі об’єкти, які зберігаються в пам'яті? Чи створюєте ви великі об'єкти (наприклад, графічні класи), які мають Disposableінтерфейс і не дзвонять, dispose()коли закінчите з ним? Ви декларуєте щось на рівні класу, яке вам потрібно лише в рамках одного методу?


2

Було б краще, якщо ви описали причину, чому вам потрібен збір сміття. Якщо ви використовуєте SWT, ви можете розпоряджатися такими ресурсами, як Imageі Fontзвільнити пам'ять. Наприклад:

Image img = new Image(Display.getDefault(), 16, 16);
img.dispose();

Існують також інструменти для визначення нерозподілених ресурсів.


що робити, якщо немає жодного способу утилізації?
АріфМустафа

1
Зовсім не пов'язане з питанням! Ні, я не використовую SWT. Я викликаю метод JNI, який відкриває вікно .NET через рідний шар Delphi. У мене також є обчислювальне ядро ​​FORTRAN, яке отримує дані через рідний рівень C ++. Що це стосується нічого? Чи можу я примусити GC чи ні? Немає? :-(
Мостафа Зейналі

0

Якщо у вас не вистачає пам’яті і OutOfMemoryExceptionви отримуєте гроші, ви можете спробувати збільшити кількість місця, доступного для Java, запустивши програму, java -Xms128m -Xmx512mа не просто java. Це дасть вам початковий розмір купи 128 Мб та максимум 512 Мб, що набагато більше, ніж стандартні 32 Мб / 128 Мб.


java -Xms512M -Xmx1024M
Типовими

0

Інший варіант - не створювати нових об’єктів.

Об'єднання об'єктів відсутнє , щоб зменшити потребу в GC в Java.

Об'єднання об'єднання, як правило, не буде швидшим, ніж створення об'єктів (особливо для легких об'єктів), але це швидше, ніж збирання сміття. Якщо ви створили 10000 об'єктів, і кожен об'єкт був 16 байт. Це 160 000 байт, які GC повинен повернути. З іншого боку, якщо вам не потрібно одночасно всі 10 000, ви можете створити пул для переробки / повторного використання об'єктів, що виключає необхідність побудови нових об'єктів та усуває потребу в GC старих об'єктах.

Щось подібне (неперевірено). І якщо ви хочете, щоб він був безпечним для потоків, ви можете поміняти LinkedList на ConcurrentLinkedQueue.

public abstract class Pool<T> {
    private int mApproximateSize;
    private LinkedList<T> mPool = new LinkedList<>();

    public Pool(int approximateSize) {
        mApproximateSize = approximateSize;
    }

    public T attain() {
        T item = mPool.poll();
        if (item == null) {
            item = newInstance();
        }
        return item;
    }

    public void release(T item) {
        int approxSize = mPool.size(); // not guaranteed accurate
        if (approxSize < mApproximateSize) {
            recycle(item);
            mPool.add(item);
        } else if (approxSize > mApproximateSize) {
            decommission(mPool.poll());
        }
    }

    public abstract T newInstance();

    public abstract void recycle(T item);

    public void decommission(T item) { }

}

0

У OracleJDK 10 з G1 GC один виклик System.gc()призведе до того, що GC очистить Стару колекцію. Я не впевнений, чи працює GC негайно. Однак GC не очистить колекцію Young, навіть якщо вона System.gc()буде викликана багато разів у циклі. Щоб GC очистив колекцію Young, ви повинні виділити її в циклі (наприклад new byte[1024]) без виклику System.gc(). Виклик System.gc()чомусь заважає GC прибирати колекцію Young.


0

Дійсно, я не розумію тебе. Але щоб бути зрозумілим щодо "Нескінченного створення об'єктів", я мав на увазі, що в моїй великій системі є якийсь фрагмент коду, який створює об'єкти, якими обробляє і живе в пам'яті, я не міг отримати цей фрагмент коду насправді, просто жестом !!

Це правильно, лише жест. У вас майже стандартні відповіді, які вже дано декількома плакатами. Візьмемо це по черзі:

  1. Я фактично не міг отримати цей фрагмент коду

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

  1. якийсь фрагмент коду в моїй великій системі займається створенням об'єктів

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

Якщо вам більше не потрібен об'єкт, ви можете призначити об’єкт null, але якщо ви помилитесь, створюється виключення з нульовим покажчиком. Б'юсь об заклад, що ви можете досягти кращої роботи, якщо будете використовувати NIO

Щоразу, коли ви або я чи хтось інший потрапляє: " Будь ласка, мені це страшенно потрібно. ". Це майже універсальний попередник майже повного знищення того, над чим ви намагаєтесь працювати. Напишіть нам невеликий зразок коду, який від нього очистить. власне використаний код та покажіть нам своє запитання.

Не засмучуйся. Часто для цього вирішується, що ваш dba використовує куплений десь пакет, і оригінальний дизайн не налаштований на масивні структури даних.

Це дуже часто.


-1

FYI

Виклик методу System.runFinalizersOnExit (true) гарантує, що методи завершення будуть викликані до того, як Java вимкнеться. Однак цей метод за своєю суттю не є безпечним і був устареним. Альтернативою є додавання "гачок відключення" методом Runtime.addShutdownHook.

Масаррат Сіддікі


Вада з гачками відключення полягає в тому, що вони рідко реально працюють. Примусове завершення роботи не працює, ненульовий код виходу не працює, а іноді (офіційний) JVM просто не запускає їх до тих пір, поки вам потрібні.
ThePyroEagle

-1

Існує певний непрямий спосіб примусити сміттєзбірник. Вам просто потрібно заповнити купу тимчасовими предметами до того моменту, коли сміттєзбірник виконає. Я створив клас, який змушує сміттєзбірник таким чином:

class GarbageCollectorManager {

    private static boolean collectionWasForced;
    private static int refCounter = 0;

    public GarbageCollectorManager() {
        refCounter++;
    }

    @Override
    protected void finalize() {
        try {
            collectionWasForced = true;
            refCounter--;
            super.finalize();   
        } catch (Throwable ex) {
            Logger.getLogger(GarbageCollectorManager.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public int forceGarbageCollection() {
        final int TEMPORARY_ARRAY_SIZE_FOR_GC = 200_000;
        int iterationsUntilCollected = 0;
        collectionWasForced = false;

        if (refCounter < 2) 
            new GarbageCollectorManager();

        while (!collectionWasForced) {
            iterationsUntilCollected++;
            int[] arr = new int[TEMPORARY_ARRAY_SIZE_FOR_GC];
            arr = null;
        }

        return iterationsUntilCollected;
    }

}

Використання:

GarbageCollectorManager manager = new GarbageCollectorManager();
int iterationsUntilGcExecuted = manager.forceGarbageCollection();

Я не знаю, наскільки цей метод корисний, оскільки він наповнює купу постійно, але якщо у вас є критично важливе застосування, яке ПОВИНЕН змусити GC - коли це може бути портативний спосіб Java для примушування GC.


Що таке "кінцевий int TEMPORARY_ARRAY_SIZE_FOR_GC = 200_000;"
Корай Тугай

Розмір масиву - скільки тимчасових об’єктів (int's) буде створено, щоб GC почала працювати.
Агній Василіяускас

Чи дійсно це: "_" у цілому?
Корай Тугай

1
Так, підкреслення в числових літералах є дійсними, починаючи з Java SE 7. Це корисно, наприклад, як роздільник тисяч у цілому, як у цьому випадку.
Агній Василіяускас

3
Ніколи не слід запускати такий код у виробничій системі. Хоча цей код працює в одному потоці, будь-який інший потік також може отримати OutOfMemoryException, повністю відміняючи намір викликати це в першу чергу ....
Steffen Heil

-1

Я хотів би додати тут щось. Зауважте, що Java працює на віртуальній машині, а не на фактичній машині. У віртуальної машини є свій спосіб спілкування з машиною. Він може змінюватись від системи до системи. Тепер, коли ми зателефонуємо в GC, ми попросимо віртуальну машину Java викликати Garbage Collector.

Оскільки збирач сміття працює з віртуальною машиною, ми не можемо змусити його проводити очищення там і далі. Швидше, що ми ставимо чергу на наш запит зі смітником. Це залежить від віртуальної машини через певний час (це може змінюватися від системи до системи, як правило, коли порогова пам'ять, виділена JVM, заповнена), фактично машина звільнить простір. : D


Перше речення другого абзацу не є послідовником .
Маркіз Лорнський

-1

Наступний код взято з методу assertGC (...). Він намагається змусити недетермінований збирач сміття збирати.

   List<byte[]> alloc = new ArrayList<byte[]>();
   int size = 100000;
   for (int i = 0; i < 50; i++) {
        if (ref.get() == null) {
             // Test succeeded! Week referenced object has been cleared by gc.
             return;
        }
        try {
             System.gc();
        } catch (OutOfMemoryError error) {
             // OK
        }
        try {
             System.runFinalization();
        } catch (OutOfMemoryError error) {
             // OK
        }

        // Approach the jvm maximal allocatable memory threshold
        try {
             // Allocates memory.
             alloc.add(new byte[size]);

             // The amount of allocated memory is increased for the next iteration.
             size = (int)(((double)size) * 1.3);
        } catch (OutOfMemoryError error) {
             // The amount of allocated memory is decreased for the next iteration.
             size = size / 2;
        }

        try {
             if (i % 3 == 0) Thread.sleep(321);
        } catch (InterruptedException t) {
             // ignore
        }
   }

   // Test failed! 

   // Free resources required for testing
   alloc = null;

   // Try to find out who holds the reference.
   String str = null;
   try {
        str = findRefsFromRoot(ref.get(), rootsHint);
   } catch (Exception e) {
        throw new AssertionFailedErrorException(e);
   } catch (OutOfMemoryError err) {
        // OK
   }
   fail(text + ":\n" + str);

Джерело (я додав кілька коментарів для ясності): Приклад NbTestCase


-1

Ви можете спробувати скористатись Runtime.getRuntime().gc()або використати корисний метод System.gc()Примітка. Ці методи не забезпечують GC. І їх сфера застосування повинна бути обмежена JVM, а не програмним поводженням з ним у вашій програмі.


2
Як пояснено в інших відповідях, ці методи не змушують (повного) вивезення сміття.
Потік

-2

Якщо ви використовуєте JUnit та Spring, спробуйте додати це у кожен тестовий клас:

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