Hi,

I checked the behavior of Objects.requireNonNull(this) at appropriate place instead of Reference.reachabilityFence(this) and it does seem to work. For example in the following test (see method m()):


import java.util.Objects;
import java.util.concurrent.atomic.AtomicLong;

public class ReachabilityTest {
    private static final AtomicLong seq = new AtomicLong();
    private static final AtomicLong deallocatedHwm = new AtomicLong();

    private static long allocate() {
        return seq.incrementAndGet();
    }

    private static void deallocate(long address) {
        deallocatedHwm.accumulateAndGet(address, Math::max);
    }

    private static void access(long address) {
        if (deallocatedHwm.get() == address) {
            throw new IllegalStateException(
                "Address " + address + " has allready been deallocated");
        }
    }

    private long address = allocate();

    @SuppressWarnings("deprecation")
    @Override
    protected void finalize() throws Throwable {
        deallocate(address);
        address = 0;
    }

    void m() {
        long a = address;
        if (a != 0) {
            System.gc();
            try { Thread.sleep(1); } catch (InterruptedException e) {}
            access(a);
            // Reference.reachabilityFence(this);
            Objects.requireNonNull(this);
        }
    }

    static void test() {
        new ReachabilityTest().m();
    }

    public static void main(String[] args) {
        while (true) {
            test();
        }
    }
}


...Objects.requireNonNull does prevent otherwise reliable provoked failure, just like Reference.reachabilityFence.

As to the speed of optimized-away Objects.requireNonNull() vs. Reference.reachabilityFence() I tried the following benchmark:


package jdk.test;

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Level;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;

import java.lang.ref.Reference;
import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 5)
@Measurement(iterations = 10)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(1)
@State(Scope.Thread)
public class ReachabilityBench {

    static class Buf0 {
        final byte[] buf;

        Buf0(int len) {
            buf = new byte[len];
        }

        @SuppressWarnings("deprecation")
        @Override
        protected void finalize() throws Throwable {
            Arrays.fill(buf, (byte) -1);
        }

        byte get(int i) {
            byte[] b = buf;
            // this might get finalized before accessing the b element
            byte r = b[i];
            return r;
        }
    }

    static class Buf1 {
        final byte[] buf;

        Buf1(int len) {
            buf = new byte[len];
        }

        @SuppressWarnings("deprecation")
        @Override
        protected void finalize() throws Throwable {
            Arrays.fill(buf, (byte) -1);
        }

        byte get(int i) {
            byte[] b = buf;
            byte r = b[i];
            Reference.reachabilityFence(this);
            return r;
        }
    }

    static class Buf2 {
        final byte[] buf;

        Buf2(int len) {
            buf = new byte[len];
        }

        @SuppressWarnings("deprecation")
        @Override
        protected void finalize() throws Throwable {
            Arrays.fill(buf, (byte) -1);
        }

        byte get(int i) {
            byte[] b = buf;
            byte r = b[i];
            Objects.requireNonNull(this);
            return r;
        }
    }

    Buf0 buf0;
    Buf1 buf1;
    Buf2 buf2;

    @Param({"64"})
    public int len;

    @Setup(Level.Trial)
    public void setup() {
        buf0 = new Buf0(len);
        buf1 = new Buf1(len);
        buf2 = new Buf2(len);
    }

    @Benchmark
    public int sumBuf0() {
        int s = 0;
        for (int i = 0; i < len; i++) {
            s += buf0.get(i);
        }
        return s;
    }

    @Benchmark
    public int sumBuf1() {
        int s = 0;
        for (int i = 0; i < len; i++) {
            s += buf1.get(i);
        }
        return s;
    }

    @Benchmark
    public int sumBuf2() {
        int s = 0;
        for (int i = 0; i < len; i++) {
            s += buf2.get(i);
        }
        return s;
    }
}


The results are as follows:

Benchmark                  (len)  Mode  Cnt    Score   Error  Units
ReachabilityBench.sumBuf0     64  avgt   10   20.530 ± 0.052  ns/op
ReachabilityBench.sumBuf1     64  avgt   10  184.402 ± 0.531  ns/op
ReachabilityBench.sumBuf2     64  avgt   10   20.502 ± 0.027  ns/op


Objects.requireNonNull() shows zero overhead here.

I guess the main question is whether Objects.requireNonNull(this) behavior in the former test is a result of chance and current Hotspot behavior or is it somehow guaranteed by the spec.


Regards, Peter


On 02/03/2018 02:04 AM, Peter Levart wrote:
Hi,

I have one question, maybe stupid. I'm wondering about one situation. Suppose you have this Java code:

void m() {
    // code before...

    Objects.requireNonNull(this);
}

Of course, the non-null check will never throw NPE. The check will most likely even be optimized away by JIT. But is JIT allowed to optimize it away so that the "code before" observes the effects of 'this' being unreachable? Wouldn't that violate the semantics of the program as it would 1st observe that some object is not reachable and after that it would observer that 'this' still points to some object which by definition must be it?

If JIT is not allowed to optimize the null check so that the object becomes unreachable, then Objects.requireNonNull could be used as a replacement for Reference.reachabilityFence. It might even perform better.

What do you think?

Regards, Peter


On 02/01/18 23:50, Paul Sandoz wrote:
Hi Ben,

I don’t think you require the fence in all the cases you have currently placed it e.g. here for example

         $memtype$ y = $toBits$(x);
         UNSAFE.put$Memtype$Unaligned(null, a, y, bigEndian);
+        Reference.reachabilityFence(this);
         return this;

since “this” is being returned it will be kept live during the unsafe access.

Would you mind providing a JMH benchmark and results for the performance with and without the fence for say a put and/or a get. I would like to understand the performance impact on HotSpot, this is one reason why we have yet to add such fences as it will likely impact performance.

At the moment we are relying on the method not being inlined, which is an expedient technique to make it functional and keep a reference alive but not necessarily optimal for usages in DBB.

For more details see:

   https://bugs.openjdk.java.net/browse/JDK-8169605 <https://bugs.openjdk.java.net/browse/JDK-8169605>    https://bugs.openjdk.java.net/browse/JDK-8149610 <https://bugs.openjdk.java.net/browse/JDK-8149610>

Thanks,
Paul.

On Feb 1, 2018, at 6:55 AM, Ben Walsh <ben_wa...@uk.ibm.com> wrote:

This contribution forms a partial solution to the problem detailed here - http://thevirtualmachinist.blogspot.ca/2011/07/subtle-issue-of-reachability.html
.

In this context, this contribution provides "markers" such that a suitably "aware" compiler can reduce the chances of such a problem occurring with
each of the Direct<Type>Buffer<RW><BO> objects and MappedByteBuffer
object. The reachabilityFences may prevent crashes / exceptions due to
cleaning up the backing memory before the user has finished using the
pointer.

Any compiler not suitably "aware" could be modified to make use of the
"markers".


I would like to pair with a sponsor to contribute this patch ...

-------------------------------------------------------------------------------------------------------------------
diff -r d51e64840b4f
src/java.base/share/classes/java/nio/Direct-X-Buffer-bin.java.template
---
a/src/java.base/share/classes/java/nio/Direct-X-Buffer-bin.java.template
Wed Jan 31 12:04:53 2018 +0800
+++
b/src/java.base/share/classes/java/nio/Direct-X-Buffer-bin.java.template
Thu Feb 01 11:30:10 2018 +0000
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights
reserved.
+ * Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights
reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -33,15 +33,21 @@

     private $type$ get$Type$(long a) {
         $memtype$ x = UNSAFE.get$Memtype$Unaligned(null, a, bigEndian);
-        return $fromBits$(x);
+        $type$ y = $fromBits$(x);
+        Reference.reachabilityFence(this);
+        return y;
     }

     public $type$ get$Type$() {
-        return get$Type$(ix(nextGetIndex($BYTES_PER_VALUE$)));
+        $type$ y = get$Type$(ix(nextGetIndex($BYTES_PER_VALUE$)));
+        Reference.reachabilityFence(this);
+        return y;
     }

     public $type$ get$Type$(int i) {
-        return get$Type$(ix(checkIndex(i, $BYTES_PER_VALUE$)));
+        $type$ y = get$Type$(ix(checkIndex(i, $BYTES_PER_VALUE$)));
+        Reference.reachabilityFence(this);
+        return y;
     }

#end[rw]
@@ -50,6 +56,7 @@
#if[rw]
         $memtype$ y = $toBits$(x);
         UNSAFE.put$Memtype$Unaligned(null, a, y, bigEndian);
+        Reference.reachabilityFence(this);
         return this;
#else[rw]
         throw new ReadOnlyBufferException();
diff -r d51e64840b4f
src/java.base/share/classes/java/nio/Direct-X-Buffer.java.template
--- a/src/java.base/share/classes/java/nio/Direct-X-Buffer.java.template
Wed Jan 31 12:04:53 2018 +0800
+++ b/src/java.base/share/classes/java/nio/Direct-X-Buffer.java.template
Thu Feb 01 11:30:10 2018 +0000
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights
reserved.
+ * Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights
reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -28,6 +28,7 @@
package java.nio;

import java.io.FileDescriptor;
+import java.lang.ref.Reference;
import jdk.internal.misc.VM;
import jdk.internal.ref.Cleaner;
import sun.nio.ch.DirectBuffer;
@@ -312,6 +313,7 @@
     public $Type$Buffer put($type$ x) {
#if[rw]
         UNSAFE.put$Swaptype$(ix(nextPutIndex()), $swap$($toBits$(x)));
+        Reference.reachabilityFence(this);
         return this;
#else[rw]
         throw new ReadOnlyBufferException();
@@ -321,6 +323,7 @@
     public $Type$Buffer put(int i, $type$ x) {
#if[rw]
         UNSAFE.put$Swaptype$(ix(checkIndex(i)), $swap$($toBits$(x)));
+        Reference.reachabilityFence(this);
         return this;
#else[rw]
         throw new ReadOnlyBufferException();
@@ -347,6 +350,7 @@
             if (srem > rem)
                 throw new BufferOverflowException();
             UNSAFE.copyMemory(sb.ix(spos), ix(pos), (long)srem <<
$LG_BYTES_PER_VALUE$);
+            Reference.reachabilityFence(this);
             sb.position(spos + srem);
             position(pos + srem);
         } else if (src.hb != null) {
@@ -413,6 +417,7 @@
         int rem = (pos <= lim ? lim - pos : 0);

         UNSAFE.copyMemory(ix(pos), ix(0), (long)rem <<
$LG_BYTES_PER_VALUE$);
+        Reference.reachabilityFence(this);
         position(rem);
         limit(capacity());
         discardMark();
@@ -505,6 +510,7 @@
     void _put(int i, byte b) {                  // package-private
#if[rw]
         UNSAFE.putByte(address + i, b);
+        Reference.reachabilityFence(this);
#else[rw]
         throw new ReadOnlyBufferException();
#end[rw]
diff -r d51e64840b4f
src/java.base/share/classes/java/nio/MappedByteBuffer.java
--- a/src/java.base/share/classes/java/nio/MappedByteBuffer.java Wed Jan
31 12:04:53 2018 +0800
+++ b/src/java.base/share/classes/java/nio/MappedByteBuffer.java Thu Feb
01 11:30:10 2018 +0000
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights
reserved.
+ * Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights
reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -26,6 +26,7 @@
package java.nio;

import java.io.FileDescriptor;
+import java.lang.ref.Reference;
import jdk.internal.misc.Unsafe;


@@ -164,6 +165,7 @@
         // is computed as we go along to prevent the compiler from
otherwise
         // considering the loop as dead code.
         Unsafe unsafe = Unsafe.getUnsafe();
+        Reference.reachabilityFence(this);
         int ps = Bits.pageSize();
         int count = Bits.pageCount(length);
         long a = mappingAddress(offset);
-------------------------------------------------------------------------------------------------------------------


Regards,
Ben Walsh


Unless stated otherwise above:
IBM United Kingdom Limited - Registered in England and Wales with number
741598.
Registered office: PO Box 41, North Harbour, Portsmouth, Hampshire PO6 3AU




Reply via email to