7

我有以下代码:

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由 in 中的弱引用引用的 '列表,slaves 并且作为副作用消除了在 中的弱引用null引用slaves。然后是依赖于每个成员slaves引用的排序T;否则代码产生一个NullPointerException.

由于在每个inunwrapped上都有一个引用,因此在排序期间没有 GC 消除一个. 最后,消除 unwrapped 上的引用,从而再次释放 GC。似乎工作得很好。TslavesTunwrapped = null

现在我的问题:

如果我在某些负载下运行许多测试时删除unwrapped = null;此结果。NullPointerExceptions我怀疑 JIT 消除了List<T> unwrapped = unwrap(); ,因此 GCT在排序期间适用于从属中的 's。

你还有别的解释吗?如果您同意我的观点,这是 JIT 中的错误吗?

我个人认为unwrapped = null应该没有必要,因为一返回unwrapped就从框架中移除updateOrdering()。是否有规范可以优化哪些不是?

还是我做错了事?我有想法修改比较器,它允许在null. 你怎么看?

感谢您的建议。

添加 (1)

现在我想补充一些缺失的信息: 首先是 Java 版本:java 版本“1.7.0_45” OpenJDK Runtime Environment (IcedTea 2.4.3) (suse-8.28.3-x86_64) OpenJDK 64-Bit Server VM (build 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;
}

请注意,在迭代时,将删除 void 引用。事实上我用这个方法代替了

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)

现在观察到,即使 updateOrdering() 中存在“unwrapped = null”,也确实发生了 NPE。

如果在 jit 优化后没有严格的引用,弱引用可能会被 java 运行时删除。源代码似乎根本不重要。

我通过以下方式解决了这个问题:

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

没有插入任何装饰以防止从属设备被垃圾收集,并且 CMP_IDX_SLV 中的比较器启用以处理对 null 的弱引用:

    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;将 void 弱引用放在列表的末尾,以后可以在那里收集。

4

3 回答 3

2

我检查了您的源代码,当 JIT 编译与您的方法“updateOrdering”相对应的方法并且在排序期间发生 GC 时,我得到了 NullPointerException。

但是当 Collections.sort 有或没有 unwrapped = null 时,我得到了 NullPointerException。这可能会出现我的示例源代码和您的示例源代码之间的差异,或者 Java 版本的差异。我会检查你是否告诉Java版本。

我使用低于版本的java。

java版本“1.7.0_40”
Java(TM) SE 运行时环境 (build 1.7.0_40-b43)
Java HotSpot(TM) 64 位服务器 VM(内部版本 24.0-b56,混合模式)

如果你想在 JIT 编译上作弊,下面的代码插入你的源代码,而不是 unwrapped = null(eg)。然后,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.
}

我的考试成绩如下。

  • 如果 JIT 不优化我对应 updateOrdering 的方法,则不会发生 NullPointerException。
  • 如果 JIT 优化我的方法,那么 NullPointerException 会在某个时候发生。
  • 如果 JIT 优化我的方法插入上述源代码欺骗 JIT 编译器,则不会发生 NullPointerException。

所以,我(和你)建议 JIT optimze 消除未包装的代码,然后发生 NullPointerException。

顺便说一句,如果要显示 JIT 编译器优化,可以使用 -XX:+PrintCompilation 调用 java。
如果要显示 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;
        }
    }
}
于 2013-12-28T13:58:43.623 回答
1

从 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在完成之前阻止收集或其元素sortreachabilityFence(的效果似乎在时间上向后传播的奇怪方式是因为它主要表现为 JIT 指令。)没有reachabilityFence,unwrapped一旦 JIT 可以证明它永远不会被再次访问,即使变量仍在范围。

于 2019-02-18T23:17:43.793 回答
0

你的问题

如果我删除 unwrapped = null; 这导致在NullPointerException某些负载下运行许多测试时。

根据我的理解,我认为这没有unwrapped = null; 任何区别。
是的,我还读到过,objects = null有时会增加引用的对象被 GC 处理的可能性,但我认为这并不重要,因为一旦方法结束,范围unwrapped就会结束并且有资格进行 GC 处理,并且在你的函数排序中Collections.sort(this.slaves, CMP_IDX_SLV);之前完成,unwrapped = null;因此在添加或删除它们时获得 NPE 是没有意义的。

我认为你得到 NPE 只是一个巧合,我相信如果你再次运行测试,你也会得到带有该语句的 NPE。

如果您阅读Java 文档

弱引用对象,不会阻止它们的引用对象被最终化、最终化,然后被回收。弱引用最常用于实现规范化映射。
假设垃圾收集器在某个时间点确定一个对象是弱可达的。那时,它将原子地清除对该对象的所有弱引用以及对通过强引用和软引用链可以访问该对象的任何其他弱可达对象的所有弱引用。同时它将声明所有以前的弱可达对象都是可终结的。在同一时间或稍后的某个时间,它会将那些在引用队列中注册的新清除的弱引用排入队列。

List因此,当您从unwrap()一些可能已标记的对象构造finalized并在您Collection.sort工作WeakRefrence时分配一些对象时,确实有可能nullMattias 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`
于 2013-12-28T13:36:06.617 回答