Являются ли невидимые ссылки все еще проблемой в недавних JVM?

StackOverflow https://stackoverflow.com/questions/271613

Вопрос

Я читал производительность платформы Java (к сожалению, ссылка, кажется, исчезла из Интернета, так как я изначально задал этот вопрос), и раздел A.3.3 меня обеспокоил.

Я работал над предположением, что переменная, выпавшая из области видимости, больше не будет считаться корнем GC, но эта статья, по-видимому, противоречит этому.

У последних JVM, в частности версии Sun 1.6.0_07, все еще есть это ограничение? Если так, то у меня много кода для анализа ...

Я задаю вопрос, потому что статья написана в 1999 году - иногда все меняется, особенно в мире GC.

<Ч>

Поскольку документ больше не доступен, я хотел бы перефразировать проблему. В документе подразумевается, что переменные, которые были определены внутри метода, будут считаться корнем GC до тех пор, пока метод не завершится, а не до окончания блока кода. Поэтому установка переменной в значение null была необходима, чтобы разрешить сборку объекта, на который ссылаются,

Это означало, что локальная переменная, определенная в условном блоке в методе main () (или аналогичном методе с бесконечным циклом), будет вызывать одноразовую утечку памяти, если вы не обнуляете переменную непосредственно перед ее выпадением из области видимости. .

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

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

Это было полезно?

Решение

Этот код должен очистить его:

public class TestInvisibleObject{
  public static class PrintWhenFinalized{
    private String s;
    public PrintWhenFinalized(String s){
      System.out.println("Constructing from "+s);
      this.s = s;
    }
    protected void finalize() throws Throwable {
      System.out.println("Finalizing from "+s);
    }   
  }
  public static void main(String[] args) {
    try {
        PrintWhenFinalized foo = new PrintWhenFinalized("main");
    } catch (Exception e) {
        // whatever
    }
    while (true) {
      // Provoke garbage-collection by allocating lots of memory
      byte[] o = new byte[1024];
    } 
  }
}

На моей машине (jdk1.6.0_05) она печатает:

  

Построение из основного

     

Завершение от основного

Похоже, проблемы были исправлены.

Обратите внимание, что использование System.gc () вместо цикла не приводит к тому, что объект по какой-то причине собирается.

Другие советы

В статье говорится, что:

  

... эффективная реализация   JVM вряд ли обнулит ссылку   когда он выходит за рамки

Я думаю, что это происходит из-за таких ситуаций:

public void doSomething() {  
    for(int i = 0; i < 10 ; i++) {
       String s = new String("boo");
       System.out.println(s);
    }
}

Здесь та же ссылка используется «эффективной JVM». в каждом объявлении String, но в куче будет 10 новых строк, если GC не включится.

В примере статьи я думаю, что ссылка на foo сохраняется в стеке, потому что " эффективная JVM " считает , что весьма вероятно, что будет создан другой объект foo, и, если это так, он будет использовать ту же ссылку. Мысли ???

public void run() {
    try {
        Object foo = new Object();
        foo.doSomething();
    } catch (Exception e) {
        // whatever
    }
    while (true) { // do stuff } // loop forever
}

Я также выполнил следующий тест с профилированием:

public class A {

    public static void main(String[] args) {
        A a = new A();  
        a.test4();
    }

    public void test1() {  
        for(int i = 0; i < 10 ; i++) {
           B b = new B();
           System.out.println(b.toString());
        }
        System.out.println("b is collected");
    }

    public void test2() {
        try {
            B b = new B();
            System.out.println(b.toString());
        } catch (Exception e) {
        }
        System.out.println("b is invisible");
    }

    public void test3() {
        if (true) {
            B b = new B();
            System.out.println(b.toString());
        }
        System.out.println("b is invisible");
    }

    public void test4() {
        int i = 0;
        while (i < 10) {
            B b = new B();
            System.out.println(b.toString());
            i++;
        }
        System.out.println("b is collected");
    }

    public A() {
    }

    class B {
        public B() {
        }

        @Override
        public String toString() {
            return "I'm B.";
        }
    }
}

и прийти к выводам:

teste1 - > б собрано

teste2 - > б невидим

teste3 - > б невидим

teste4 - > б собрано

... так что я думаю, что в циклах JVM не создает невидимые переменные, когда цикл заканчивается, поскольку маловероятно, что они будут объявлены снова вне цикла.

Есть мысли ??

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

Я бы совсем не удивился, если бы он был незафиксирован в данный момент, но я не думаю, что он будет настолько значительным, насколько вы, похоже, боитесь.

Проблема все еще там. Я проверил это на Java & nbsp; 8 и смог доказать это.

Вы должны отметить следующее:

<Ол>
  • Единственный способ форсировать гарантированную сборку мусора - это попробовать выделение, которое заканчивается OutOfMemoryError, поскольку JVM требуется для освобождения неиспользуемых объектов перед выбросом. Это, однако, не выполняется, если запрашиваемая сумма слишком велика для успеха, т. Е. Превышает адресное пространство. Попытка увеличить распределение до получения OOME - хорошая стратегия.

  • Гарантированный GC, описанный в пункте 1, не гарантирует завершение. Время, когда вызывается метод finalize (), не указано, они могут вообще никогда не вызываться. Поэтому добавление метода finalize () в класс может помешать его экземплярам собираться, поэтому finalize не является хорошим выбором для анализа поведения GC.

  • Создание другой новой локальной переменной после того, как локальная переменная вышла из области видимости, будет повторно использовать ее место в кадре стека. В следующем примере объект a будет собран, так как его место в кадре стека занято локальной переменной b. Но b продлится до конца основного метода, так как нет другой локальной переменной, которая могла бы занять его место.

    import java.lang.ref.*;
    
    public class Test {
        static final ReferenceQueue<Object> RQ=new ReferenceQueue<>();
        static Reference<Object> A, B;
        public static void main(String[] s) {
            {
                Object a=new Object();
                A=new PhantomReference<>(a, RQ);
            }
            {
                Object b=new Object();
                B=new PhantomReference<>(b, RQ);
            }
            forceGC();
            checkGC();
        }
    
        private static void forceGC() {
            try {
                for(int i=100000;;i+=i) {
                  byte[] b=new byte[i];
                }
            } catch(OutOfMemoryError err){ err.printStackTrace();}
        }
    
        private static void checkGC() {
            for(;;) {
                Reference<?> r=RQ.poll();
                if(r==null) break;
                if(r==A) System.out.println("Object a collected");
                if(r==B) System.out.println("Object b collected");
            }
        }
    }
    
  • Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top