JIT-оптимизация и слабые ссылки

У меня есть следующий фрагмент кода:

private final List<WeakReference<T>> slaves;

public void updateOrdering() {
  // removes void weak references 
  // and ensures that weak references are not voided 
  // during subsequent sort 
  List<T> unwrapped = unwrap();
  assert unwrapped.size() == this.slaves.size();
  // **** could be reimplemented without using unwrap() ****
  Collections.sort(this.slaves, CMP_IDX_SLV);
  unwrapped = null;// without this, ....
}

Метод unwrap() просто создает список T, на которые ссылаются слабые ссылки в slaves, и в качестве побочного эффекта устраняет слабые ссылки, ссылающиеся на null в slaves. Затем идет сортировка, основанная на том, что каждый член slaves ссылается на какой-то T; в противном случае код дает NullPointerException.

Поскольку unwrapped содержит ссылку на каждый T в slaves, во время сортировки ни один GC не удаляет T. Наконец, unwrapped = null удаляет ссылку на unwrapped и, таким образом, снова освобождает сборщик мусора. Кажется, работает довольно хорошо.

Теперь мой вопрос:

Если я удалю unwrapped = null;, это приведет к NullPointerExceptions при выполнении многих тестов под некоторой нагрузкой. Я подозреваю, что JIT устраняет List<T> unwrapped = unwrap();, и поэтому GC применяется к T в ведомых устройствах во время сортировки.

У вас есть другое объяснение? Если вы согласны со мной, это ошибка в JIT?

Я лично считаю, что unwrapped = null не должен быть нужен, потому что unwrapped убирается из кадра, как только возвращается updateOrdering(). Есть ли спецификация, что можно оптимизировать, а что нет?

Или я сделал что-то не так? У меня есть идея модифицировать компаратор, чтобы он допускал слабые ссылки на null. Что ты об этом думаешь?

Спасибо за предложения.

Добавить (1)

Теперь я хочу добавить недостающую информацию: Прежде всего версия Java: версия Java "1.7.0_45" Среда выполнения OpenJDK (IcedTea 2.4.3) (suse-8.28.3-x86_64) OpenJDK 64-разрядная виртуальная машина сервера (сборка 24.45-b08, смешанный режим)

Затем кто-то захотел увидеть, как разворачивается метод

private synchronized List<T> unwrap() {
List<T> res = new ArrayList<T>();
T cand;
WeakReference<T> slvRef;
Iterator<WeakReference<T>> iter = this.slaves.iterator();
while (iter.hasNext()) {
    slvRef = iter.next();
    cand = slvRef.get();
    if (cand == null) {
    iter.remove();
    continue;
    }
    assert cand != null;
    res.add(cand);
} // while (iter.hasNext())

return res;
}

Обратите внимание, что во время итерации пустые ссылки удаляются. На самом деле я заменил этот метод на

private synchronized List<T> unwrap() {
List<T> res = new ArrayList<T>();
for (T cand : this) {
    assert cand != null;
    res.add(cand);
}

return res;
}

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

Затем кто-то хочет трассировку стека. Вот часть этого.

 java.lang.NullPointerException: null
 at WeakSlaveCollection$IdxComparator.compare(WeakSlaveCollection.java:44)
 at WeakSlaveCollection$IdxComparator.compare(WeakSlaveCollection.java:40)
 at java.util.TimSort.countRunAndMakeAscending(TimSort.java:324)
 at java.util.TimSort.sort(TimSort.java:189)
 at java.util.TimSort.sort(TimSort.java:173)
 at java.util.Arrays.sort(Arrays.java:659)
 at java.util.Collections.sort(Collections.java:217)
 at WeakSlaveCollection.updateOrdering(WeakSlaveCollection.java:183)

он указывает на компаратор, линию с возвратом.

static class IdxComparator 
    implements Comparator<WeakReference<? extends XSlaveNumber>> {
    public    int compare(WeakReference<? extends XSlaveNumber> slv1, 
              WeakReference<? extends XSlaveNumber> slv2) {
        return slv2.get().index()-slv1.get().index();
    }
} // class IdxComparator 

и наконец,

 private final static IdxComparator CMP_IDX_SLV = new IdxComparator();

является важной константой.

Добавить (2)

Теперь замечено, что NPE действительно происходит, даже если в updateOrdering() присутствует 'unwrapped = null'.

Слабые ссылки могут быть удалены средой выполнения Java, если строгая ссылка не сохраняется после JIT-оптимизации. Исходный код кажется вообще не важным.

Я решил проблему следующим образом:

public void updateOrdering() {
Collections.sort(this.slaves, CMP_IDX_SLV);
}

без каких-либо украшений, вставленных для предотвращения сборки мусора подчиненных устройств, и компаратор в CMP_IDX_SLV, включенный для обработки слабых ссылок на ноль:

    public    int compare(WeakReference<? extends XSlaveNumber> slv1, 
              WeakReference<? extends XSlaveNumber> slv2) {
    XSlaveNumber sSlv1 = slv1.get();
    XSlaveNumber sSlv2 = slv2.get();
    if (sSlv1 == null) {
    return sSlv2 == null ? 0 : -1;
    }
    if (sSlv2 == null) {
    return +1;
    }
    assert sSlv1 != null && sSlv2 != null;

    return sSlv2.index()-sSlv1.index();
    }

В качестве побочного эффекта упорядочивание базового списка List>slaves; помещает пустые слабые ссылки в конец списка, откуда их можно будет собрать позже.


person user2609605    schedule 28.12.2013    source источник
comment
Я сомневаюсь, что он удаляет сам вызов unwrap(), но если unwrapped никогда не используется впоследствии, он может быть собран. В любом случае, нулевое присваивание или нет, оно зависит от деталей реализации сборщика мусора.   -  person kiheru    schedule 28.12.2013
comment
Это всего лишь теория, но компилятор может заметить, что возвращаемое значение unwrap() на самом деле не используется, и оптимизировать его, не сохраняя его (полностью исключая переменную unwrapped). Что бы вы ни пробовали, вы всегда проиграете в борьбе с компилятором. Что вы могли сделать, так это отсортировать развернутый список, а затем заново его обернуть.   -  person Mattias Buelens    schedule 28.12.2013
comment
Покажите нам трассировку стека и метод unwrap. Проблема скорее всего в вашем коде, а не в JIT.   -  person user2357112 supports Monica    schedule 28.12.2013
comment
Рассматривали ли вы расширение класса WeakReference, чтобы он содержал информацию, необходимую для операции сортировки?   -  person Sami Korhonen    schedule 28.12.2013
comment
Даже наполовину действительный JITC не устранит вызов метода (кроме встраивания). Это было бы главным нет-нет. Но с JIT или без него развернутый список может пройти сборку мусора, как только на него больше не будет ссылок в будущем. Скорее всего, этого не происходит в случае, отличном от JITC, из-за того, как работает стек Java, но в определении языка нет ничего, что могло бы это предотвратить.   -  person Hot Licks    schedule 29.12.2013
comment
И всегда есть вероятность того, что слабая ссылка будет очищена, даже если объект, на который она ссылается, все еще существует. GC делает это в определенных ситуациях.   -  person Hot Licks    schedule 29.12.2013
comment
хорошо, я обновил свое мнение: вероятно, это присвоение возвращаемого значения вызова метода, а не вызов как таковой.   -  person user2609605    schedule 30.12.2013
comment
@hotlicks хорошо, ссылку на слабую ссылку не следует очищать, если на нее все еще есть сильная ссылка. unwrapped предоставляет такую ​​ссылку, по крайней мере, без оптимизации.. верно? по крайней мере во время сортировки.   -  person user2609605    schedule 30.12.2013
comment
Это то, что вы думаете, не так ли.   -  person Hot Licks    schedule 30.12.2013
comment
да. и что ты думаешь? ;-)   -  person user2609605    schedule 31.12.2013
comment
Это указано в JLS. §12.6.1: «Оптимизирующие преобразования программы могут быть разработаны таким образом, чтобы уменьшить количество достижимых объектов по сравнению с теми, которые наивно считались бы достижимыми.». Вы можете просто отсортировать свой список unwrapped, а затем воссоздать список WeakReference. Это не только решило бы проблему, но и упростило бы код, так как даже метод unwrap не требует изменения входящего списка и т. д.   -  person Holger    schedule 08.02.2018


Ответы (3)


Я изучаю ваш исходный код и получаю исключение NullPointerException, когда JIT-компилирует мой метод, соответствующий вашему методу «updateOrdering», и во время сортировки происходит GC.

Но я получил NullPointerException, когда Collections.sort независимо от того, с развернутым или без него = null. Возможно, это связано с разницей между исходным кодом моего примера и вашим или с разницей в версии Java. Я проверю, если вы сообщите версию Java.

Я использую java ниже версии.

java version "1.7.0_40"
Java(TM) SE Runtime Environment (build 1.7.0_40-b43)
Java HotSpot(TM) 64-Bit Server VM (build 24.0-b56, mixed mode)

Если вы хотите обмануть компиляцию JIT, приведенный ниже код вместо этого вставит ваш исходный код unwrapped = null (например). Тогда JIT-компиляция не устраняет неупакованный код.

long value = unwrapped.size() * unwrapped.size();
if(value * value % 3 == 1) {
  //Because value * value % 3 always is 1 or 0, this code can't reach. 
  //Insert into this the source code that use unwrapped array, for example, show unwrapped array.
}

Результат моего обследования ниже.

  • If JIT don't optimize my method corresponding to updateOrdering, no NullPointerException occurs.
  • If JIT optimize my method, then NullPointerException occurs at some point.
  • If JIT optimize my method inserting the above source code cheating JIT compiler, then no NullPointerException occurs.

Итак, я (и вы) предлагаете JIT optimze исключить развернутый код, тогда возникает NullPointerException.

Кстати, если вы хотите показать оптимизацию компилятора JIT, вы вызываете java с помощью -XX:+PrintCompilation.
Если вы хотите показать GC, с -verbose:gc.

Просто для информации, мой пример исходного кода ниже.

public class WeakSampleMain {
    private static List<WeakReference<Integer>> weakList = new LinkedList<>();
    private static long sum = 0;
    public static void main(String[] args) {
        System.out.println("start");
        int size = 1_000_000;
        for(int i = 0; i < size; i++) {
            Integer value = Integer.valueOf(i);
            weakList.add(new WeakReference<Integer>(value));
        }
        for(int i = 0; i < 10; i++) {
            jitSort();
        }
        GcTask gcTask = new GcTask();
        Thread thread = new Thread(gcTask);
        thread.start();
        for(int i = 0; i < 100000; i++) {
            jitSort();
        }
        thread.interrupt();
        System.out.println(sum);
    }

    public static void jitSort() {
        List<Integer> unwrappedList = unwrapped();
        removeNull();
        Collections.sort(weakList, 
                new Comparator<WeakReference<Integer>>() {

                    @Override
                    public int compare(WeakReference<Integer> o1,
                            WeakReference<Integer> o2) {
                        return Integer.compare(o1.get(), o2.get());
                    }
        }
                );
        for(int i = 0; i < Math.min(weakList.size(), 1000); i++) {
            sum += weakList.get(i).get();
        }
        unwrappedList = null;
//          long value = (sum + unwrappedList.size());
//          if((value * value) % 3 == 2) {
//              for(int i = 0; i < unwrappedList.size(); i++) {
//                  System.out.println(unwrappedList.get(i));
//              }
//          }
    }

    public static List<Integer> unwrapped() {
        ArrayList<Integer> list = new ArrayList<Integer>();
        for(WeakReference<Integer> ref : weakList) {
            Integer i = ref.get();
            if(i != null) {
                list.add(i);
            }
        }
        return list;
    }

    public static void removeNull() {
        Iterator<WeakReference<Integer>> itr = weakList.iterator();
        while(itr.hasNext()) {
            WeakReference<Integer> ref = itr.next();
            if(ref.get() == null) {
                itr.remove();
            }
        }
    }

    public static class GcTask implements Runnable {
        private volatile int result = 0;
        private List<Integer> stockList = new ArrayList<Integer>();
        public void run() {
            while(true) {
                if(Thread.interrupted()) {
                    break;
                }
                int size = 1000000;
                stockList = new ArrayList<Integer>(size);
                for(int i = 0; i < size; i++) {
                    stockList.add(new Integer(i));
                }
                if(System.currentTimeMillis() % 1000 == 0) {
                    System.out.println("size : " + stockList.size());
                }
            }
        }

        public int getResult() {
            return result;
        }
    }
}
person t_ozawa    schedule 28.12.2013
comment
ваши комментарии очень помогли. Из экспериментов я никогда не могу быть уверен, что unwrapped = null защищает от сборки мусора. Это может зависеть от определенных аспектов моего окружения. Вот почему я спросил, есть ли спецификация, что оптимизировано, а что нет. То, что ваш эксперимент вышел другим, может указывать на отсутствие спецификации. Ну... Спецификация просто говорит, что слабые ссылки не подвергаются сборке мусора, пока есть сильные ссылки. Но это слабо сформулировано: unwrapped предоставляет сильные ссылки в исходном коде Java, но если он оптимизирован... - person user2609605; 29.12.2013
comment
кстати, я добавил некоторую полезную информацию к моему вопросу, который вы просили. - person user2609605; 30.12.2013

Начиная с Java 9, правильный способ предотвратить отбрасывание JIT unwrapped — использовать Reference.reachabilityFence:

public void updateOrdering() {
  List<T> unwrapped = unwrap();
  Collections.sort(this.slaves, CMP_IDX_SLV);
  Reference.reachabilityFence(unwrapped);
}

Наличие вызова reachabilityFence приводит к тому, что unwrapped считается сильно достижимым до вызова, предотвращая сбор unwrapped или его элементов до завершения sort. (Странный способ, которым эффекты reachabilityFence, кажется, распространяются назад во времени, заключается в том, что он ведет себя в основном как JIT-директива.) Без reachabilityFence unwrapped можно собрать, как только JIT сможет доказать, что к ней никогда больше не будет доступа, даже если переменная все еще в области.

person user2357112 supports Monica    schedule 18.02.2019

Ваш вопрос

Если я удалю unwrapped = null; это приводит к NullPointerException при выполнении многих тестов под некоторой нагрузкой.

Насколько я понимаю, я не думаю, что unwrapped = null; имеет какое-либо значение.
Да, я также читал, что создание objects = null иногда увеличивает вероятность того, что объект, на который ссылаются, будет подвергнут сборке мусора, но я не думаю, что здесь это имеет значение, потому что один раз метод заканчивается, область unwrapped заканчивается и подходит для GC'ed, а в вашей функции сортировка Collections.sort(this.slaves, CMP_IDX_SLV); выполняется до unwrapped = null;, поэтому нет смысла получать NPE при их добавлении или удалении.

Я думаю, что это просто совпадение, что вы получаете NPE, я считаю, что если вы снова запустите тест, вы также получите NPE с этим утверждением.

Если вы читали документацию по Java

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

Так что вполне возможно, что когда вы создавали List из unwrap(), некоторые объекты могли быть помечены finalized, а пока ваш Collection.sort работает, некоторым WeakRefrence присваивается null. И точка зрения, заявленная Mattias Buelens, совершенно верна, вы всегда будете проиграть в борьбе с компилятором.

Если вы согласны со мной, это ошибка в JIT?

Нет, конечно, я с вами совершенно не согласен.

У меня есть идея изменить comparator, чтобы он разрешал слабые ссылки на null. Что ты об этом думаешь?

Я думаю, что это решит вашу единственную проблему NPE, но ваше требование removes void weak references and ensures that weak references are not voided during subsequent sort не удовлетворено.
Лучше попробуйте еще раз вызвать unwrap, это уменьшит окно для NPE почти до нуля,

List<T> unwrapped = unwrap();
unwrapped = unwrap(); //Again to eliminate the chances for NPE as now we would have 
           //already made strong refrences to all objects which have not been `null`
person dbw    schedule 28.12.2013
comment
Прежде всего спасибо за все хорошие ответы. Сначала я дам информацию, которую многим из вас не хватало. - person user2609605; 29.12.2013
comment
Проще всего предоставить: версия java: версия java 1.7.0_45 OpenJDK Runtime Environment (IcedTea 2.4.3) (suse-8.28.3-x86_64) 64-битная виртуальная машина сервера OpenJDK (сборка 24.45-b08, смешанный режим) - person user2609605; 29.12.2013
comment
Метод unwrap теперь изменен с использованием собственного итератора, но должен быть эквивалентен следующему: ‹code› частный синхронизированный список‹T› unwrap() { List‹T› res = new ArrayList‹T›(); Тканд; WeakReference‹T› slvRef; Iterator‹WeakReference‹T›› iter = this.slaves.iterator(); в то время как (iter.hasNext()) { slvRef = iter.next(); cand = slvRef.get(); if (cand == null) { iter.remove(); Продолжать; } res.add(канд); } // while (iter.hasNext()) return res; } ‹/код› - person user2609605; 29.12.2013
comment
хорошо, вот мой настоящий комментарий к вашему первому разделу: «поэтому нет смысла получать NPE при их добавлении или удалении. ' Не могли бы вы быть более конкретным? Как вы думаете, NPE не может произойти? или что это происходит в обеих ситуациях? - person user2609605; 29.12.2013
comment
Это может произойти как в ситуации с этим оператором, так и без него..... - person dbw; 30.12.2013
comment
хорошо, теперь я также заметил проблему с unwrapped==null - person user2609605; 31.12.2013
comment
Кстати, я думаю, что без jit-оптимизации мой код работает хорошо, верно? - person user2609605; 31.12.2013