Подтвердить что ты не робот

Как возможно ArrayOutOfBoundsException в String.valueOf(int)?

Почему этот код иногда вызывает ArrayOutOfBoundsException? Как это возможно даже для String.valueOf(int)?

public static String ipToString(ByteString bs) {
  if (bs == null || bs.isEmpty()) {
    return null;
  } else {
    StringBuilder sb = new StringBuilder();
    boolean started = false;
    for (Byte byt : bs) {
      if (started) {
        sb.append(".");
      }
      sb.append(String.valueOf(byt & 0xFF));
      started = true;
    }

    return sb.toString();
  }
}


java.lang.ArrayIndexOutOfBoundsException: -81914
  at java.lang.Integer.getChars(Integer.java:458)
  at java.lang.Integer.toString(Integer.java:402)
  at java.lang.String.valueOf(String.java:3086)
  at com.mystuff.mypackage.ipToString(MyCode.java:1325)
  ...
  at java.util.concurrent.FutureTask.run(FutureTask.java:266)
  at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
  at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
  at java.lang.Thread.run(Thread.java:745)

Обновление

Я не знаю значения байта, когда это происходит, но не похоже, что это возможно для любого возможного значения байта.

Как только это происходит один раз, каждый вызов затем выдает ошибки с тем же исключением.

Окружающая среда:

java version "1.8.0_20"
Java(TM) SE Runtime Environment (build 1.8.0_20-b26)
Java HotSpot(TM) 64-Bit Server VM (build 25.20-b23, mixed mode)
4b9b3361

Ответ 1

Это ошибка компилятора JIT, которая была введена в JDK 8u20 как побочный эффект другого исправления:
JDK-8042786

Проблема связана с оптимизацией оптимизации бокса.
Обход заключается в отключении оптимизации с помощью -XX:-EliminateAutoBox флага JVM

Похоже, проблема также существует в самой последней исходной базе JDK 9.
Я отправил отчет об ошибке: https://bugs.openjdk.java.net/browse/JDK-8058847 со 100% воспроизводимым минимальным тестовым случаем.

Ответ 2

Я могу достоверно воспроизвести вашу проблему с помощью этого кода:

public class Main
{
  public static StringBuilder intToString(byte[] bs) {
    final StringBuilder sb = new StringBuilder();
    boolean started = false;
    for (Byte byt : bs) {
      if (started) sb.append(".");
      sb.append(String.valueOf(byt & 0xFF));
      started = true;
    }
    return sb;
  }

  public static void main(String[] args) {
    final byte[] bs = {-2, -1, 0, 1, 2};
    while (true) intToString(bs);
  }
}

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

Если это доступно вам, вы можете активировать диагностические параметры JVM, которые будут печатать все события компиляции (-XX:PrintCompilation). Тогда вы можете сопоставить такое событие с моментом появления исключения.

Ответ 3

Я оставляю здесь фрагмент кода, так как он все равно должен выполняться быстрее исходного кода - при стоимости памяти - но имейте в виду, что на самом деле не устраняет проблему.

private static final String[] STRING_CACHE = new String[256];

static {
  for(int i = 0; i <= 255; i++) {
    STRING_CACHE[i] = String.valueOf(i);
  }
}

public static String ipToString(ByteString bs) {
  if (bs == null || bs.isEmpty()) {
    return null;
  } else {
    StringBuilder sb = new StringBuilder();
    boolean started = false;
    for (Byte byt : bs) {
      if (started) {
        sb.append(".");
      }
      sb.append(STRING_CACHE[byt & 0xFF]);
      started = true;
    }

    return sb.toString();
  }
}