【问题标题】:OutOfMemoryError when seemingly unrelated code block commented out注释掉看似无关的代码块时出现 OutOfMemoryError
【发布时间】:2014-01-29 17:02:29
【问题描述】:

有人可以解释为什么当 for 循环被注释掉时,这个程序会抛出一个 OutOfMemoryError 吗?如果未注释,则运行良好。

抛出的异常是:

线程“主”java.lang.OutOfMemoryError 中的异常:Java 堆空间

public class JavaMemoryPuzzlePolite
{
    private final int dataSize = (int)(Runtime.getRuntime().maxMemory()* 0.6);

    public void f()
    {
        {
            System.out.println(dataSize);
            byte[] data = new byte[dataSize];
        }

        /*
        for(int i = 0; i < 10; i++) {
            System.out.println("Please be so kind and release memory");
        }
        */

        System.out.println(dataSize);
        byte[] data2 = new byte[dataSize];
    }

    public static void main(String []args)
    {
        JavaMemoryPuzzlePolite jmp = new JavaMemoryPuzzlePolite();
        jmp.f();
    }
}

【问题讨论】:

  • 在我的机器上编译得很好,不管有没有注释代码
  • 为什么f()后面多了一个括号?
  • @mdewitt 这只是一个块。
  • 线程“主”java.lang.OutOfMemoryError 中的异常:Java 堆空间
  • 在这两种情况下都能正确编译。它在未注释循环的情况下正常运行,但对我来说失败了 OutOfMemoryError 并且循环被注释掉了。如果您更清楚地缩进代码,当然会更容易理解,但这很好。如果你要说有错误,你应该总是说明错误是什么。

标签: java out-of-memory


【解决方案1】:

我已经用许多不同类型的代码 sn-ps 对此进行了调查,这些代码可以插入到您的注释所在的位置,唯一不会导致 OutOfMemoryError 的代码类型是为局部变量分配一些值的代码。

这是对我来说最有意义的解释:

当你有

byte[] data = new byte[dataSize];

bytecode instructions

    12: newarray       byte
    14: astore_1 

newarray 创建一个新数组,astore_1 在局部变量 1 中存储对它的引用。

在此之后,该变量的范围丢失了,但字节码没有说明其值被清除的任何内容,因此堆栈帧中保留了对该对象的引用。这个特定的垃圾收集器认为它是可访问的,即使代码本身无法访问它。

如果您尝试分配另一个局部变量,例如

byte i = 1;

那么对应的字节码指令是这样的

    15: iconst_1      
    16: istore_1   

其中iconst_1 将值 1 存储在堆栈中,istore_1 将该值存储在变量 1 中,这似乎与之前的变量相同。如果是,那么您将覆盖它的值,对 byte[] 对象的引用丢失,然后该对象“变得”有资格进行垃圾回收。

最终证明

使用-g 选项编译此代码

public class Driver {
    private static final int dataSize = (int) (Runtime.getRuntime().maxMemory() * 0.6);

    public static void main(String[] args) throws InterruptedException {
        {
            System.out.println(dataSize);
            byte[] data = new byte[dataSize];

        }
        byte i = 1;
        System.out.println(dataSize);
        byte[] data2 = new byte[dataSize];
    }

}

然后运行javap -c -l Driver。你会看到像这样的LocalVariableTable

LocalVariableTable:
  Start  Length  Slot  Name   Signature
   15       0     1    data   [B
    0      33     0    args   [Ljava/lang/String;
   17      16     1     i     B
   32       1     2    data2  [B

其中 slot 是 astore_1istore_1 中的索引。所以你看,当你为局部变量分配一个新值时,对byte[] 的引用被清除了。即使变量具有不同的类型/名称,在字节码中,它们也存储在同一个位置。

【讨论】:

  • +1:我不确定,但是对 System.out.println() 的调用使用相同的局部变量?
  • @MartijnCourteaux dataSize?这是一个实例变量。
  • 不,我的意思是在 println 执行的某个地方使用了相同的插槽。但是现在我看到int i int 循环可能是使用相同槽的变量。
  • en.wikipedia.org/wiki/Graph_coloring#Algorithms 这里有一些算法可以用来给槽分配变量。图的顶点是变量,槽由颜色表示。顶点之间的边意味着两个连接的变量同时处于活动状态。
  • 真的很高兴阅读这个答案。我知道GC的一些行为和能力规则,但这只是解释了这个问题的真正原因:stackoverflow.com/a/19482891/155137
【解决方案2】:

正如 cmets 中用户 Sotirios Delimanolis 所链接的答案所暗示的,这与第一个 data 数组的范围有关。简化一点,代码

{
    byte[] data = new byte[bigNumber];
}
byte[] data = new byte[bigNumber];

编译为(简体并使用英文)

1: make an array of size bigNumber
2: store a reference to the created array into the 1st local variable
3: make an array of size bigNumber
4: store a reference to the created array into the 1st local variable

这会耗尽内存,因为在第三步 60% 的内存已经被占用,我们正在尝试创建一个占用 60% 更多内存的数组。旧的字节数组不能被垃圾回收,因为它仍然在第一个局部变量中被引用。

但是代码

{
    byte[] data = new byte[bigNumber];
}
someOtherVariable = somethingSmall;
byte[] data = new byte[bigNumber];

编译成

1: make an array of size bigNumber
2: store a reference to the created array into the 1st local variable
3: make somethingSmall
4: store a reference to somethingSmall into the 1st local variable
5: make an array of size bigNumber
6: store a reference to the created array into the 2nd local variable

在第四步,初始大字节数组被取消引用。因此,当您尝试在第五步创建另一个大数组时,它会成功,因为它可以垃圾收集旧数组以腾出空间。

【讨论】:

    【解决方案3】:

    当您取消注释 for 循环时,在执行循环时,JVM 有足够的时间释放分配给 data 的内存,因为变量的范围已经结束并且变量变得不可访问。

    当您注释掉 for 循环时,JVM 不会有太多时间来释放变量 data 使用的内存

    【讨论】:

    • 请注意,在 for 循环中,我们声明了一个新变量 i。在为i 分配内存时,GC 正在垃圾收集分配给字节数组data 的内存。如果变量是在定义data之前定义的i,并取消注释for循环,即使这样你也会得到OutOfMemoryError
    • 变量和GC有什么关系?你的回答谈到了时间。在什么意义上?
    猜你喜欢
    • 2012-02-24
    • 2015-09-30
    • 1970-01-01
    • 1970-01-01
    • 2019-11-08
    • 2011-08-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多