JVM错误?缓存对象字段值导致 ArrayIndexOutOfBoundsException
Posted
技术标签:
【中文标题】JVM错误?缓存对象字段值导致 ArrayIndexOutOfBoundsException【英文标题】:JVM bug? Cached Object field value cause ArrayIndexOutOfBoundsException 【发布时间】:2013-06-19 18:57:08 【问题描述】:这有点奇怪,但代码比文字更能说话,所以看看测试看看我在做什么。在我当前的设置(Windows 64 位上的 Java 7 更新 21)中,此测试因 ArrayIndexOutOfBoundsException 而失败,但是用注释代码替换测试方法代码,它可以工作。我想知道 Java 规范中是否有任何部分可以解释原因。
在我看来,正如“michael nesterenko”所建议的那样,数组字段的值在调用方法之前被缓存在堆栈中,并且在调用返回时不会更新。我不知道它是 JVM 错误还是记录在案的“优化”。不涉及多线程或“魔法”。
public class TestAIOOB
private String[] array = new String[0];
private int grow(final String txt)
final int index = array.length;
array = Arrays.copyOf(array, index + 1);
array[index] = txt;
return index;
@Test
public void testGrow()
//final int index = grow("test");
//System.out.println(array[index]);
System.out.println(array[grow("test")]);
【问题讨论】:
只是一个猜测,当您从数组中调用grow
时,它已经在堆栈中,因此链接不会更新,但是如果您之前调用grow
然后使用索引,则会加载指向数组的链接在它更新之后,它就可以工作了。只是一个猜测。也许查看字节码可能会有所帮助
我不会称它为“缓存在堆栈中”。我认为这是一个Java(语言)问题。您正在引用一个名称,该名称在之前使用/应用与其关联的已解析值进行解析。因此,如果在两者之间重新分配名称,则您的值是错误的。
这是一个更简单的测试用例:public class TestAIOOB static Object[] array; static int reassign() array = new Object[] new Object() ; return 0; public static void main(String[] args) System.out.println(array[reassign()]);
。这抛出(就像它应该的那样),你问为什么。
值得一提的是,它不仅仅是Java。数组引用是在 javascript (example)、C# (example) 和 C (example) 中的 grow
调用之前评估的,可能还有其他的,但我没有尝试过。 (您可以在此处在线运行最后两个中的任何一个:compileonline.com)
【参考方案1】:
这由Java Language Specification 很好地定义:要评估x[y]
,首先评估x
,然后评估y
。在您的情况下,x
评估为具有零元素的String[]
。然后,y
修改一个成员变量,并计算为0
。尝试访问已返回数组的第 0 个元素失败。成员 array
更改的事实与数组查找无关,因为我们正在查看 String[]
在评估时引用的 array
。
【讨论】:
链接中的第一个要点。很棒。【参考方案2】:这种行为是 JLS 规定的。根据15.13.1,“使用以下过程评估数组访问表达式:首先,评估数组引用表达式。如果此评估突然完成,那么数组访问会出于同样的原因而突然完成,并且不会评估索引表达式。否则,将评估索引表达式。[...]"。
【讨论】:
【参考方案3】:使用javap -c TestAIOOB
比较编译后的Java代码
未注释的代码:
public void testGrow();
Code:
0: getstatic #6; //Field java/lang/System.out:Ljava/io/PrintStream;
3: aload_0
4: getfield #3; //Field array:[Ljava/lang/String;
7: aload_0
8: ldc #7; //String test
10: invokespecial #8; //Method grow:(Ljava/lang/String;)I
13: aaload
14: invokevirtual #9; //Method java/io/PrintStream.println:(Ljava/lang/St
ing;)V
17: return
注释代码:
public void testGrow();
Code:
0: aload_0
1: ldc #6; //String test
3: invokespecial #7; //Method grow:(Ljava/lang/String;)I
6: istore_1
7: getstatic #8; //Field java/lang/System.out:Ljava/io/PrintStream;
10: aload_0
11: getfield #3; //Field array:[Ljava/lang/String;
14: iload_1
15: aaload
16: invokevirtual #9; //Method java/io/PrintStream.println:(Ljava/lang/Str
ing;)V
19: return
第一个getfield
发生在调用grow
之前,第二个发生在之后。
【讨论】:
这解释了行为,但没有解释为什么会这样编译。 JLS 里面一定有什么东西……以上是关于JVM错误?缓存对象字段值导致 ArrayIndexOutOfBoundsException的主要内容,如果未能解决你的问题,请参考以下文章
因在缓存对象中增加字段,导致Redis出现反序列化失败的问题
因在缓存对象中增加字段,而导致Redis中取出缓存转化成Java对象时出现反序列化失败的问题