Что означает поле BUF_OFFSET в BufferedInputStream?

Это поле.

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

Это код, использующий 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;
}

Это код в исходном коде JDK о возврате значения 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);
}

Что означает поле BUF_OFFSET в BufferedInputStream?

Я проверяю BUF_OFFSET в исходном коде JDK. гитхаб:https://github.com/openjdk/jdk/tree/jdk-17%2B35 Я задал вопрос о том, что здесь находится JavaFieldStream. Но я все еще не понимаю BUF_OFFSET.

Я думаю ... Может быть, BUF_OFFSET похож на параметр off, который находится в методе чтения FileInputStream?

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

Здесь off означает, что данные копируются и заполняются из массива C в массив Java. Таким образом, BUF_OFFSET означает, где данные заполняют JVM? Это только мое предположение.

Не спамить тегами. Как C был связан с этим вопросом

0___________ 21.11.2022 09:46

Исходный код JDK взят из c

cuo yiban 21.11.2022 10:00

В самом начале в BufferedInputStream есть комментарий: «Поскольку этот класс используется на ранней стадии начальной загрузки, он мотивирован использовать Unsafe.compareAndSetObject вместо AtomicReferenceFieldUpdater (или VarHandles), чтобы уменьшить зависимости и сократить время запуска».

Stefan Zobel 21.11.2022 17:09

@StefanZobel Конечно, я знаю комментарий, я просто хочу узнать о нем больше подробностей.

cuo yiban 22.11.2022 06:47

Что ж, этот комментарий должен, по крайней мере, ответить на ваш вопрос «Почему необходимо использовать метод compareAndSetReference?» вопрос.

Stefan Zobel 22.11.2022 08:37

Не задавайте два разных вопроса одновременно. «Что означает поле BUF_OFFSET» и «Почему необходимо использовать compareAndSetReference» совершенно не связаны.

Holger 22.11.2022 09:42

@Holger хорошо, я изменил его. Только чтобы сначала спросить, что такое BUF_OFFSET.

cuo yiban 22.11.2022 10:50

Это крайняя деталь реализации, о которой вам не следует беспокоиться, если только вы не собираетесь вносить свой вклад в сам OpenJDK. Как я уже говорил ранее, как начинающий Java-программист, почему вы вообще пытаетесь это прочитать? Вам не нужно читать код реализации Java, чтобы использовать его. Вы, скорее всего, просто потеряетесь в таких деталях, и на самом деле это не научит вас хорошо программировать/использовать Java.

Mark Rotteveel 22.11.2022 10:55

Как уже говорилось в комментарии, процитированном Стефаном Зобелем, этот класс намеренно использует не AtomicReferenceFieldUpdater и VarHandle, а низкоуровневый класс с именем Unsafe для атомарного обновления. Эта альтернатива не инкапсулирует объект Field или его эквивалент, но требует необработанного смещения в объект, чтобы определить поле (местоположение в памяти) для обновления. Вот почему вы видите BUF_OFFSET = U.objectFieldOffset(BufferedInputStream.class, "buf") → найти смещение поля buf и U.compareAndSetReference(this, BUF_OFFSET, …) → обновить поле buf текущего объекта (this).

Holger 22.11.2022 10:55

@Holger Могу ли я понять так: у buf есть смещение, у нового более длинного массива nbuf тоже есть смещение. U.compareAndSetReference должен сделать смещение BUF_OFFSET=nbuf, чтобы позволить buf = nuf в следующей строке.

cuo yiban 22.11.2022 11:52

@MarkRotteveel Спасибо за внимание и советы! Мой уровень Java, вероятно, для программирования в Google. Я знаю правила и грамматику Java. Когда я начинаю писать код, я не знаком с функциями в деталях, поэтому сейчас читаю исходный код. И это, без сомнения, вызовет много проблем при чтении, но, к счастью, я нашел сайт-сокровище - stackoverflow. Поэтому я начинаю задавать вопросы здесь.

cuo yiban 22.11.2022 12:07

"U.compareAndSetReference должен сделать BUF_OFFSET=смещение nbuf ...". Нет, совсем нет. Вызов compareAndSetReference атомарно обновляет переменную-член buf на nbuf, если она в настоящее время содержит buffer. Ничего другого здесь не происходит.

Stefan Zobel 22.11.2022 13:26

@StefanZobel Но «buffer = nbuf» было написано после «if (! U.compareAndSetReference)», что произойдет, если я удалю оператор if?

cuo yiban 22.11.2022 14:03

«buffer=nbuf» устанавливает buffer, а не buf

Stefan Zobel 22.11.2022 14:21

@StefanZobel Я искал слово: «AtomicReferenceFieldUpdater» в Google, и я понимаю, что оно использовалось для изменения значения изменчивого поля. Итак, «compareAndSetReference» делает почти то же самое? Не разрешайте buf=nbuf напрямую, поэтому требуется «compareAndSetReference», чтобы позволить nbuf заменить buf.

cuo yiban 22.11.2022 19:56

Это if buf == buffer buf = nbuf;, но атомарно. Это как compareAndSet в AtomicReferenceFieldUpdater. Важно не только то, что поле изменчиво, но и то, что весь процесс сравнения (чтения) поля с ожидаемым значением (buffer) и, если оно совпадает, установка поля в его новое значение (nbuf) является atomic операцией. Как говорится в комментарии, это можно было сделать с помощью AtomicReferenceFieldUpdater за счет загрузки дополнительных классов во время запуска JVM (замедление запуска), и именно этого они хотели избежать, используя небезопасный API.

Stefan Zobel 22.11.2022 20:52

@StefanZobel Я наконец понял! Спасибо за ваше терпение. Я хочу принять ваш ответ. Хотите ли вы скопировать свои комментарии в столбец ответов, чтобы я мог их принять?

cuo yiban 23.11.2022 05:45
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
17
117
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

BUF_OFFSET — это просто смещение поля buf в объекте BufferedInputStream, которое требуется для доступа к этому полю с помощью метода Unsafe.compareAndSetReference. Поскольку использование (неофициально задокументированного) класса sun.misc.Unsafe подразумевает, что это низкоуровневый код, который небезопасно взаимодействует с предположениями об объектах и ​​их доступе.

Этот метод, в свою очередь, используется как альтернатива AtomicReferenceFieldUpdater, как описано в комментарии в начале класса:

Поскольку этот класс используется на ранних этапах начальной загрузки, рекомендуется использовать Unsafe.compareAndSetObject вместо AtomicReferenceFieldUpdater (или VarHandles), чтобы уменьшить количество зависимостей и сократить время запуска.

Ответ принят как подходящий

Метод fill() заполняет внутренний элемент buf дополнительными данными, заменяя его другим массивом другого размера, когда это необходимо. Вызовы fill() защищены внутренней блокировкой (или синхронизированы, если BufferedInputStream является подклассом). Когда InputStream закрывается, для buf устанавливается значение null, чтобы указать, что поток закрыт. Однако вызовы close() могут быть асинхронными в том смысле, что функция close() не защищена от одновременного доступа, как вызовы fill(). Следовательно, возникает необходимость проверить, является ли buf == null (поток закрыт) в fill(), и выполнить if (buf == buffer) buf = null; (buf не был изменен одновременно fill()) в close() потокобезопасным (атомарным) способом.

Это делается с помощью атомарных инструкций CAS (сравнить и поменять местами / сравнить и установить), которые сравнивают содержимое ячейки памяти с заданным значением и, только если они совпадают, изменяют содержимое этой ячейки на присваивается новое значение как одна атомарная операция. Атомарность гарантирует, что запись зависит от того, является ли текущее значение актуальным (т. е. не измененным тем временем другим потоком).

В Java это можно сделать несколькими способами: классы AtomicXyzFieldUpdater в пакете j.u.concurrent.atomic, API j.l.invoke VarHandle, представленный в Java 9, и внутренний Unsafe API JDK, не предназначенный для публичного использования.

Поскольку BufferedInputStream используется на ранней стадии начальной загрузки JVM (это означает фазу инициализации при запуске JVM), важно: а) избегать использования API, которые могли не быть инициализированы в то время (что может привести к циклам зависимости) и б) избегать замедляет начальную загрузку с помощью API, которые должны быть сначала инициализированы, чтобы их можно было использовать. В этом причина использования внутреннего Unsafe API.

Небезопасный метод, о котором мы говорим, заключается в следующем:

/**
 * 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);

Первый аргумент — это экземпляр объекта, поле которого должно быть установлено, второй аргумент — это смещение поля, которое должно быть обновлено в этом объекте (по сути, относительный адрес памяти), третий аргумент — это значение, которое мы ожидаем в настоящее время. это поле, а четвертый аргумент — это значение, которое мы хотим сохранить в этом поле, если наше ожидание окажется правильным.

Смещение поля можно определить с помощью

/**
 * 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);
}

Смещение поля — это константа, которую необходимо определить только один раз, и обычно она хранится в static final long. Это и есть BUF_OFFSET (смещение поля buf внутри экземпляра BufferedInputStream).

Итак, (потокобезопасный) код

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

логически эквивалентен (однопоточному) коду

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

Единственная разница в том, что compareAndSetReference является атомарным, а последний код — нет.

Идеальный ответ!

cuo yiban 23.11.2022 15:21

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