Подтвердить что ты не робот

Что будет делать поток Finalizer, если в методе завершения Java существует бесконечный цикл или тупик

что будет делать поток Finalizer, если в завершающем методе Java бесконечный цикл или тупик..

4b9b3361

Ответ 1

Спектр пишет:

Прежде чем хранилище для объекта будет возвращено сборщиком мусора, виртуальная машина Java вызовет финализатор этого объекта.

Язык программирования Java не указывает, как скоро будет вызываться финализатор, кроме как сказать, что это произойдет до того, как хранилище будет повторно использовано для объекта.

Я прочитал это, чтобы означать, что финализатор должен быть выполнен до того, как хранилище может быть повторно использовано.

Язык программирования Java не указывает, какой поток будет вызывать финализатор для любого заданного объекта.

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

Таким образом, финализация может произойти в потоке коллектора мусора, в отдельном аэде или даже в отдельном пуле потоков.

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

Следующая тестовая программа подтверждает это поведение:

public class Test {

    byte[] memoryHog = new byte[1024 * 1024];

    @Override
    protected void finalize() throws Throwable {
        System.out.println("Finalizing " + this + " in thread " + Thread.currentThread());
        for (;;);
    }

    public static void main(String[] args) {
        for (int i = 0; i < 1000; i++) {
            new Test();
        }
    }
}

В Oracle JDK 7 это печатает:

Finalizing [email protected] in thread Thread[Finalizer,8,system]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
        at tools.Test.<init>(Test.java:5)
        at tools.Test.main(Test.java:15)

Ответ 2

Я бы сказал, что, поскольку спецификация Java не указывает, как должен быть вызван метод finalize (просто чтобы он был вызван, прежде чем объект будет собран мусором), поведение специфично для реализации.

Спецификация не исключает наличия нескольких потоков, выполняющих этот процесс, но не требует:

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

Посмотрев на источники JDK7, FinalizerThread сохраняет очередь объектов, запланированных для финализации (на самом деле объекты добавляются в очередь GC, когда доказано, что они недоступны - проверьте ReferenceQueue doc):

private static class FinalizerThread extends Thread {
    private volatile boolean running;
    FinalizerThread(ThreadGroup g) {
        super(g, "Finalizer");
    }
    public void run() {
        if (running)
            return;
        running = true;
        for (;;) {
            try {
                Finalizer f = (Finalizer)queue.remove();
                f.runFinalizer();
            } catch (InterruptedException x) {
                continue;
            }
        }
    }
}

Каждый объект удаляется из очереди, и на нем запускается метод runFinalizer. Проверка выполняется, если завершение было выполнено на объекте, а если оно не вызывается, в качестве вызова метода native invokeFinalizeMethod. Метод просто вызывает метод finalize для объекта:

JNIEXPORT void JNICALL
Java_java_lang_ref_Finalizer_invokeFinalizeMethod(JNIEnv *env, jclass clazz,
                                                  jobject ob)
{
    jclass cls;
    jmethodID mid;

    cls = (*env)->GetObjectClass(env, ob);
    if (cls == NULL) return;
    mid = (*env)->GetMethodID(env, cls, "finalize", "()V");
    if (mid == NULL) return;
    (*env)->CallVoidMethod(env, ob, mid);
}

Это должно привести к ситуации, когда объекты попадают в очередь в списке, а FinalizerThread заблокирован на неисправном объекте, что в свою очередь должно привести к OutOfMemoryError.

Итак, чтобы ответить на исходный вопрос:

что будет делать поток Finalizer, если в методе завершения Java существует бесконечный цикл или тупик.

Он просто сидит и запускает этот бесконечный цикл до OutOfMemoryError.

public class FinalizeLoop {
    public static void main(String[] args) {
        Thread thread = new Thread() {
            @Override
            public void run() {
                for (;;) {
                    new FinalizeLoop();
                }
            }
        };
        thread.setDaemon(true);
        thread.start();
        while (true);
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("Finalize called");
        while (true);

    }
}

Обратите внимание на "Finalize called", если он напечатан только один раз на JDK6 и JDK7.

Ответ 3

Объекты не будут "освобождены", т.е. память не будет возвращена из них, а также ресурсы, освобожденные в методе finalize, будут сохраняться на всем протяжении.

В основном существует очередь, в которой хранятся все объекты, ожидающие выполнения их метода finalize(). Финализатор потока захватывает объекты из этой очереди - завершает работу - и освобождает объект.

Если этот поток будет заторможен, очередь ReferenceQueue будет расти, и в какой-то момент ошибка OOM станет неумолимой. Также ресурсы будут забиты объектами в этой очереди. Надеюсь, это поможет!

for(;;)
{
  Finalizer f = java.lang.ref.Finalizer.ReferenceQueue.remove();
  f.get().finalize();
}