0

This is the field.

private static final long BUF_OFFSET
        = U.objectFieldOffset(BufferedInputStream.class, "buf");

This is code which using BUF_OFFSET.

private void fill() throws IOException {
    byte[] buffer = getBufIfOpen();
    if (markpos < 0)
        pos = 0;            /* no mark: throw away the buffer */
    else if (pos >= buffer.length) { /* no room left in buffer */
        if (markpos > 0) {  /* can throw away early part of the buffer */
            int sz = pos - markpos;
            System.arraycopy(buffer, markpos, buffer, 0, sz);
            pos = sz;
            markpos = 0;
        } else if (buffer.length >= marklimit) {
            markpos = -1;   /* buffer got too big, invalidate mark */
            pos = 0;        /* drop buffer contents */
        } else {            /* grow buffer */
            int nsz = ArraysSupport.newLength(pos,
                    1,  /* minimum growth */
                    pos /* preferred growth */);
            if (nsz > marklimit)
                nsz = marklimit;
            byte[] nbuf = new byte[nsz];
            System.arraycopy(buffer, 0, nbuf, 0, pos);
            **if (!U.compareAndSetReference(this, BUF_OFFSET, buffer, nbuf)) {
                // Can't replace buf if there was an async close.
                // Note: This would need to be changed if fill()
                // is ever made accessible to multiple threads.
                // But for now, the only way CAS can fail is via close.
                // assert buf == null;
                throw new IOException("Stream closed");
            }**
            buffer = nbuf;
        }
    }
    count = pos;
    int n = getInIfOpen().read(buffer, pos, buffer.length - pos);
    if (n > 0)
        count = n + pos;
}

This is the code in JDK source about return the value of BUF_OFFSET.

static jlong find_field_offset(jclass clazz, jstring name, TRAPS) {
  assert(clazz != NULL, "clazz must not be NULL");
  assert(name != NULL, "name must not be NULL");

  ResourceMark rm(THREAD);
  char *utf_name = java_lang_String::as_utf8_string(JNIHandles::resolve_non_null(name));

  InstanceKlass* k = InstanceKlass::cast(java_lang_Class::as_Klass(JNIHandles::resolve_non_null(clazz)));

  jint offset = -1;
  for (JavaFieldStream fs(k); !fs.done(); fs.next()) {
    Symbol *name = fs.name();
    if (name->equals(utf_name)) {
      **offset** = fs.offset();
      break;
    }
  }
  if (offset < 0) {
    THROW_0(vmSymbols::java_lang_InternalError());
  }
  return field_offset_from_byte_offset(offset);
}

What does BUF_OFFSET field mean in BufferedInputStream?

I check the BUF_OFFSET out in JDK source code github:https://github.com/openjdk/jdk/tree/jdk-17%2B35 I have asked a question about what JavaFieldStream is in here.But I am still confused about the BUF_OFFSET.

I guess...Maybe BUF_OFFSET is simliar with the param off which is from FileInputStream's read method?

public int read(byte b[], int off, int len) throws IOException {
    return readBytes(b, off, len);
}

In here,off means where the data copys and fills in from C array to the Java array.So BUF_OFFSET means where data fills in JVM?It's just my guess.

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
cuo yiban
  • 41
  • 4
  • Do not spam tags. How was C related to this question – 0___________ Nov 21 '22 at 08:46
  • JDK source code is from c – cuo yiban Nov 21 '22 at 09:00
  • 2
    There is a comment in BufferedInputStream right at the beginning: "As this class is used early during bootstrap, it's motivated to use Unsafe.compareAndSetObject instead of AtomicReferenceFieldUpdater (or VarHandles) to reduce dependencies and improve startup time." – Stefan Zobel Nov 21 '22 at 16:09
  • @StefanZobel Of course I know the comment,I just want to know more details about it. – cuo yiban Nov 22 '22 at 05:47
  • 1
    Well, that comment should at least answer your "Why is necessary to use compareAndSetReference method?" question. – Stefan Zobel Nov 22 '22 at 07:37
  • 2
    Don’t ask two different questions at once. “What does BUF_OFFSET field mean” and “Why is necessary to use compareAndSetReference” are entirely unrelated. – Holger Nov 22 '22 at 08:42
  • @Holger ok,I have amended it.Only to ask what BUF_OFFSET is first. – cuo yiban Nov 22 '22 at 09:50
  • 1
    This is an extreme implementation detail that you shouldn't concern yourself with unless you're going to be contributing to OpenJDK itself. As I said earlier, as a new Java programmer, why are you even trying to read this? You don't need read the Java implementation code to be able to use it. You'll likely just get lost in details like this, and it will in fact not learn you how to program/use Java well. – Mark Rotteveel Nov 22 '22 at 09:55
  • 2
    As the comment cited by Stefan Zobel already told, this class is deliberately not using `AtomicReferenceFieldUpdater` nor `VarHandle`, but a lowlevel class named `Unsafe` for the atomic update. This alternative does not encapsulate a `Field` object or equivalent but requires the raw offset into the object, to identify the field (memory location) to update. That’s why you see `BUF_OFFSET = U.objectFieldOffset(BufferedInputStream.class, "buf")` → find the offset of the field `buf` and `U.compareAndSetReference(this, BUF_OFFSET, …)` → update the field `buf` of the current object (`this`). – Holger Nov 22 '22 at 09:55
  • @Holger Can I understand like this: the buf has a offset,the new longer array nbuf has a offset,too.The U.compareAndSetReference is to make BUF_OFFSET=nbuf 's offset,so that let buf = nuf in the nextline. – cuo yiban Nov 22 '22 at 10:52
  • @MarkRotteveel Thank you for your attention and advice!My Java level is probably for Google programming.I know the Java rules and grammar.When I start to write code,I am not familiar with the functions in details,that's why I am now reading source code.And that's no doubt to rise many problems when reading,but luckily I have found the treasure site--stackoverflow.So I begin to ask questions here. – cuo yiban Nov 22 '22 at 11:07
  • 2
    "The U.compareAndSetReference is to make BUF_OFFSET=nbuf 's offset ...". No, not at all. The `compareAndSetReference` call atomically updates the member variable `buf` to `nbuf` if it is currently holding `buffer`. Nothing else is happening here. – Stefan Zobel Nov 22 '22 at 12:26
  • @StefanZobel But "buffer=nbuf" has been written after "if(!U.compareAndSetReference)",what will happened if I remove the if statement? – cuo yiban Nov 22 '22 at 13:03
  • 1
    "buffer=nbuf" sets `buffer`, not `buf` – Stefan Zobel Nov 22 '22 at 13:21
  • @StefanZobel I searched the word:"AtomicReferenceFieldUpdater" in google,and I realize it was using to change the volatile field's value.So the “compareAndSetReference” nearly does the samething?I guess,because of the word "volatile",the buf couldn't let buf=nbuf directly,so it needs the “compareAndSetReference” to let nbuf replace the buf. – cuo yiban Nov 22 '22 at 18:56
  • 4
    It is `if buf == buffer buf = nbuf;` but in an atomic way. It's like `compareAndSet` in AtomicReferenceFieldUpdater. What is important is not only that the field is volatile but that the whole process of comparing (reading) the field against the expected value (`buffer`) and if that is matched setting the field to its new value (`nbuf`) is an `atomic` operation. As the comment says that could have been done with an AtomicReferenceFieldUpdater at the cost of loading additional classes during JVM startup (slowing down the startup) and that is what they wanted to avoid by using the Unsafe API. – Stefan Zobel Nov 22 '22 at 19:52
  • @StefanZobel I finally understand ! Thank you for your patience.I want to accept your answer.Would you want to copy your comments to the answer column so that I can accept it? – cuo yiban Nov 23 '22 at 04:45

2 Answers2

2

The fill() method fills the internal buf member with more data replacing it by another array of a different size when necessary. Calls to fill() are protected by an internal lock (or synchronized when BufferedInputStream is sub-classed). When the InputStream gets closed buf is set to null to indicate that the stream is closed. However, calls to close() can be asynchronous in the sense that close() is not protected against concurrent access in the way that calls to fill() are. Hence, the need arises to check whether buf == null (stream is closed) in fill() and do if (buf == buffer) buf = null; (buf hasn't been changed concurrently by fill()) in close() in a threadsafe (atomic) manner.

This is done by using atomic CAS (compare-and-swap / compare-and-set) instructions which compare the content of a memory location with a given value and, only if they are the same, modifies the content of that location to a given new value as a single atomic operation. Atomicity guarantees that the write is conditional on the current value being up-to-date (i.e., not being modified by another thread in the meantime).

There are several ways to do this in Java: the AtomicXyzFieldUpdater classes in the j.u.concurrent.atomic package, the j.l.invoke VarHandle API introduced in Java 9 and a JDK-internal Unsafe API not intended for public use.

Since BufferedInputStream is used early in the JVM bootstrap (that means the initialization phase at JVM startup) it is important to a) avoid using APIs that may not have been initialized at that time (wich can lead to dependency cycles) and b) to avoid slowing down the bootstrap by using APIs that have to be initialized first to be usable. That's the rationale for using the internal Unsafe API in this case.

The Unsafe method we are talking about is the following:

/**
 * Atomically updates Java variable to {@code x} if it is currently
 * holding {@code expected}.
 *
 * <p>This operation has memory semantics of a {@code volatile} read
 * and write.  Corresponds to C11 atomic_compare_exchange_strong.
 *
 * @return {@code true} if successful
 */
@IntrinsicCandidate
public final native boolean compareAndSetReference(Object o, long offset,
                                                   Object expected,
                                                   Object x);

The first argument is the object instance whose field should be set, the second argument is the offset of the field to be updated in that object (in effect a relative memory address), the third argument is the value that we expect to be currently in that field and the fourth argument is the value that we want to be stored in that field if our expectation turns out to be correct.

The offset of the field can be determined by using

/**
 * Reports the location of the field with a given name in the storage
 * allocation of its class.
 *
 * @throws NullPointerException if any parameter is {@code null}.
 * @throws InternalError if there is no field named {@code name} declared
 *         in class {@code c}, i.e., if {@code c.getDeclaredField(name)}
 *         would throw {@code java.lang.NoSuchFieldException}.
 *
 * @see #objectFieldOffset(Field)
 */
public long objectFieldOffset(Class<?> c, String name) {
    if (c == null || name == null) {
        throw new NullPointerException();
    }

    return objectFieldOffset1(c, name);
}

The field offset is a constant that needs to be determined only once and it is usually stored in a static final long. This is what BUF_OFFSET is (the offset of the buf field inside a BufferedInputStream instance).

So, the (threadsafe) code

if (!U.compareAndSetReference(this, BUF_OFFSET, buffer, nbuf)) {
    throw new IOException("Stream closed");
}

is logically equivalent to the (single-threaded) code

if (buf == buffer) {
    buf = nbuf;
} else {
    throw new IOException("Stream closed");
}

The only difference is that compareAndSetReference is atomic while the latter code is not.

Stefan Zobel
  • 3,182
  • 7
  • 28
  • 38
0

BUF_OFFSET is simply the offset of the buf field in the BufferedInputStream object and required to access that field using the method Unsafe.compareAndSetReference. As the usage of the (not officially documented) sun.misc.Unsafe class implies this is low-level code that unsafely interacts with assumptions about objects and their access.

This method in turn is used as an alternative to AtomicReferenceFieldUpdater as described in the comment at the beginning of the class:

As this class is used early during bootstrap, it's motivated to use Unsafe.compareAndSetObject instead of AtomicReferenceFieldUpdater (or VarHandles) to reduce dependencies and improve startup time.

Generous Badger
  • 404
  • 2
  • 10