28

Why does this code sometimes produce ArrayOutOfBoundsException? How is that even possible for 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)

Updates

I don't know the value of the byte when this occurs, but it doesn't seem like it should be possible for any possible value of byte.

Once it happens once, every invocation then errors out with the same exception.

Environment:

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)
34
  • 3
    Does this fall over outside of a multithreaded environment? The stack trace suggests you're running this as part of a thread pool. Is there a synchronization problem? Commented Sep 19, 2014 at 20:42
  • 2
    And what jdk are you using? Commented Sep 19, 2014 at 20:42
  • 5
    In case you are using IBM JVM, there seems to be a bug in the JIT compiler.. stackoverflow.com/questions/16059794/… Commented Sep 19, 2014 at 20:45
  • 4
    It does appear to be more consistent with a JITC bug than anything else. Commented Sep 19, 2014 at 20:56
  • 4
    Your last update is important: it quite cleanly points to JIT compiler issue (inlining or possibly even some assembler macro code). Commented Sep 19, 2014 at 21:19

3 Answers 3

19

This is a JIT compiler bug that has been introduced in JDK 8u20 as a side-effect of another fix:
JDK-8042786

The problem is related to auto-boxing elimination optimization.
The work-around is to switch the optimization off by -XX:-EliminateAutoBox JVM flag

Looks like the problem also exists in the most recent JDK 9 source base.
I've submitted the bug report: https://bugs.openjdk.java.net/browse/JDK-8058847 with 100% reproducible minimal test case included.

Sign up to request clarification or add additional context in comments.

1 Comment

a patch has been accepted to resolve the issue hg.openjdk.java.net/jdk9/hs-comp/hotspot/rev/7723d5b0fca3
7

I can reliably reproduce your issue with this code:

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

The issue will almost certainly be traced to a JIT compiler bug. Your observation that, once it happens the first time, it happens reliably on every subsequent call, points cleanly to a JIT compilation event which introduces the buggy code into the codepath.

If that's available to you, you could activate diagnostic JVM options which will print all compilation events (-XX:PrintCompilation). Then you may be able to correlate such an event with the moment when the exception starts appearing.

2 Comments

if the problem is caused by for(Byte b : bs) as opposed to for(byte b : bs) - see comments in my answer - can you see if you can reproduce the problem with that change?
See my update: that change makes it easily reproducible. One thing that wasn't obvious from your code was whether bs returned a byte or a Byte, and that turns out to be a key difference.
7

I am leaving the code snippet here, as it still ought to be run faster than the original code - at a cost of memory - but be advised it doesn't actually fix the problem.

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();
  }
}

8 Comments

certainly is not enough and workaround is also not asked by OP.
@KumarAbhinav From the help center How to answer: "What, specifically, is the question asking for? Make sure your answer provides that – or a viable alternative"
It should be noted that this bug is described elsewhere as having been fixed a year ago. The OP needs to update his JVM version.
@HotLicks But OP is clearly using the newest JVM available? Still, a link to the bug would be the closest to a real answer to this question!
@MarkoTopolnik - Well, I guess the fixed version (to a nearly identical bug) was in a IBM JVM, whereas this is apparently an Oracle version, so perhaps it's not the same. (Or perhaps it is the same but Oracle hasn't fixed it yet.)
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.