You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

字节数组转int出现不可预测值(Eclipse Temurin-17环境)

多线程下字节数组转int间歇性返回随机值问题

问题描述

运行多线程程序时出现异常:创建大量线程(10000个)处理同一字节数组,在高CPU占用或内存受限(如设置-Xms32m -Xmx32m)场景下,byteArrayToInt方法将字节数组特定段转为int时,间歇性返回随机值。预期liVersion值为1,但实际输出随机数;即使内存充足、线程数少(甚至单线程)仍会出现,只是频率降低。

环境信息

  • 操作系统:Windows 10
  • Java版本:Eclipse Temurin-17.0.10+11,该问题也在Eclipse Temurin-17.0.13+11、Amazon Corretto 17中复现,暂未在AdoptOpenJDK 11.0.11中发现。

代码说明

TestMain类中,compareInner方法复制字节数组的指定段([1,0]),调用byteArrayToInt转为int,预期结果为1,但间歇性输出错误值。调试时未发现问题,单线程单次调用compareInner不会触发问题。

复现条件

  • 设置-Xms32m -Xmx32m时可稳定复现;
  • 内存设为1024m时复现频率降低;
  • 单线程场景下仍会偶现;
  • 线程内多次调用compareInner时触发。

代码片段

import java.util.Arrays;

public class TestMain
{
    private static int miParallelThreadCount = 10000;

    private static long mlExecutionIntervalInMillis=100;
    
    public static void main(String[] args)
    {
        System.out.println("Startin tornado at interval: " + mlExecutionIntervalInMillis + " with parallel threads: " + miParallelThreadCount);
        while(true)
        {
            //Runs the test code to reproduce the issue
            runTornado();
            try
            {
                Thread.sleep(mlExecutionIntervalInMillis);
            } catch (Throwable lThrowable)
            {
                System.out.println("Task failed due to: " + lThrowable.getLocalizedMessage());
                lThrowable.printStackTrace();
            }
        }
    }

    private static void runTornado() 
    {   
        //Test byte array
        byte[] data = new byte[]
                { 65, 50, 1, 0, 1, 0, 36, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 16, 0, 53, -76, -66, 125, -115, 69, -111, 80, -81, -33, -62, 84, -59, -64, -114, 113, -92, -59, 37, 106, 85, -111, -100, -19, 114, 99, 18, -9, -62, -123, -75, -22, -40, 70, -70, -46, 119, 11, -17, -71, -93, -126, -28, 116, 104, -4, -45, -58, 121, 4, 50, 76, 46, 13, -91, -76, 37, -103, 101, 57, 54, 75, 83, -124 };
        
        for(int i=0; i<miParallelThreadCount; ++i)
        {
            //This snippet executes the group of compareInner methods in new threads
            new Thread(() -> 
            {
                try
                {
                    compareInner(data, 0, data.length);
                    compareInner(data, 0, data.length);
                    compareInner(data, 0, data.length);
                    compareInner(data, 0, data.length);
                    compareInner(data, 0, data.length);
                    compareInner(data, 0, data.length);
                    compareInner(data, 0, data.length);
                    compareInner(data, 0, data.length);
                    compareInner(data, 0, data.length);
                    compareInner(data, 0, data.length);
                    compareInner(data, 0, data.length);
                    compareInner(data, 0, data.length);
                    compareInner(data, 0, data.length);
                    compareInner(data, 0, data.length);
                    compareInner(data, 0, data.length);
                } 
                catch (Throwable lThrowable) 
                {
                    System.out.println("Something went wrong: " + lThrowable.getLocalizedMessage());
                    lThrowable.printStackTrace();
                }
            }).start();
        }
        System.out.println("Ran tornado");
    }

    private static byte[] compareInner(byte[] pTestData, int pStartIndex, int pLength)
    {
        //Copies the pTestData into new array as it is
        byte[] lbarrData = Arrays.copyOfRange(pTestData, 0, 0 + pTestData.length);
        //copies only 2nd and 3rd element from the array which is [1, 0]
        byte[] lbarrVer = Arrays.copyOfRange(lbarrData, 2, 4);

        //Converts the array [1, 0] into integer, which should always return 1
        int liVersion = byteArrayToInt(lbarrVer);

        if(liVersion != 1)
        {
            System.out.println("Invalid Version : " + liVersion + " data array: " + Arrays.toString(lbarrData) +  " header array: " + Arrays.toString(lbarrVer));
        }
        
        return lbarrData;
    }
    
    public static final int byteArrayToInt(byte[] pSource)
    {
        int lValue = 0;
        for (int i = 0; i < pSource.length; i++)
        {
            lValue += (pSource[i] & 0xff) << (8 * i);
        }
        return lValue;
    }
}

问题分析与解决

根本原因

这是JDK 17中C2编译器的优化bug,当JIT编译Arrays.copyOfRange相关代码时,出现内存访问错误,导致复制后的字节数组内容被意外篡改。内存受限或高并发场景下GC频繁触发,会更容易触发该优化bug。

解决方案

  1. 临时规避方案:
    • 添加JVM参数禁用C2编译器:-XX:-UseC2,仅保留C1编译器;
    • 替换Arrays.copyOfRange为手动复制逻辑,避免触发有问题的JIT优化:
      private static byte[] manualCopyRange(byte[] src, int from, int to) {
          int length = to - from;
          byte[] dest = new byte[length];
          System.arraycopy(src, from, dest, 0, length);
          return dest;
      }
      
  2. 长期方案:升级至修复该bug的JDK版本,如Eclipse Temurin 17.0.14+或更高版本,官方已在后续维护版本中修复此问题。

相关讨论内容

该问题在Adoptium支持社区中被确认是JDK 17特有的C2编译器优化问题,JDK 11未受影响。问题根源是Arrays.copyOfRange的JIT编译代码出现内存引用错误,官方已发布修复补丁,升级JDK版本即可彻底解决;临时禁用C2可立即规避问题,但会损失部分性能。


内容的提问来源于stack exchange,提问作者Amey Sawant

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 08:15:59