如何在无限循环中使用less Heap Space?

How to use less Heap Space in endless loop?

我的 java 应用程序的内存使用有问题。堆 Space 和非堆 Space。现在我专注于我的堆 Space.

我的应用程序是一个 SocketServer,它通过 DataInputStream 获取输入。我正在以字节数组的形式读取信息。我每秒收到的输入量不规律,但我们说的是 space 从每秒 400 字节到 1.000 字节,峰值可以更高。

由于我的程序是一个服务器,它会无限循环地等待输入。现在我遇到了问题,我的堆 Space 随着时间的推移在上升,所有 5-10 分钟它上升了 0.5MB。

我使用了多个监控应用程序,例如 jconsole 和 YourProfiler。在那之后,我试图借助堆转储来弄清楚,我用 jmap 生成它并用 Eclipse 内存分析器进行分析。

现在我的问题是,在此示例代码中,哪个选项更好或使用更少的堆 Space 1 或 2?

选项 1:

while (true){
byte [] one= new byte [21]; 
do something with one;
byte [] two= new byte [50];
do something with two;
byte [] three= new byte [30];
do something with three;
}

选项 2:

byte [] one;
byte [] two;
byte [] three;
while (true){
one= new byte [21]; 
do something with one;
two= new byte [50];
do something with two;
three= new byte [30];
do something with three;
}

我不知道循环中创建的三个对象发生了什么。这些应该是局部变量,并且只在循环中可见和可访问。但是在一个循环之后,JVM 会删除它们并在下一个循环中创建新的。所以我猜应该没有内存泄漏吧?

在第二个Option中,三个变量声明在循环外,这样他们就一直活着。在循环中,这些对象的引用发生变化,因此不会引用旧内容,这意味着它被删除,分别被 GC 收集。

在这两个选项中,每秒大约有 4 个圆圈。

提前感谢您的帮助!

JUnit 测试结果:

变量 onetwothree 只是引用:它们本身不保存值,而只是引用堆中实际数组所在的位置对象已存储。

因此,两种方法在分配的对象数量方面没有区别。

选项 3:在循环外分配数组,并重复使用相同的数组:

byte [] one= new byte [21];
byte [] two= new byte [50];
byte [] three= new byte [30];
while (true){
  // If necessary, zero out the arrays so that data from the previous
  // iteration is not used accidentally.
  Arrays.fill(one, (byte) 0);
  Arrays.fill(two, (byte) 0);
  Arrays.fill(three, (byte) 0);

  // Rest of the loop.
}

这会预先分配数组,因此只创建了 3 个数组对象,而不是 (3 * #iterations) 个数组对象。

请注意,只有在不泄漏对数组的引用时才能使用此方法,例如将它们放入循环体之外的列表中。


为了证明 OP 的两种方法中的内存分配是相同的,请尝试反编译代码:

  public static void inLoop() {
    while (true) {
      byte[] one = new byte[21];
      byte[] two = new byte[50];
      byte[] three = new byte[30];
    }
  }

  public static void outsideLoop() {
    byte[] one;
    byte[] two;
    byte[] three;
    while (true) {
      one = new byte[21];
      two = new byte[50];
      three = new byte[30];
    }
  }

这两种方法反编译为相同字节码:

  public static void inLoop();
    Code:
       0: bipush        21
       2: newarray       byte
       4: astore_0
       5: bipush        50
       7: newarray       byte
       9: astore_1
      10: bipush        30
      12: newarray       byte
      14: astore_2
      15: goto          0

  public static void outsideLoop();
    Code:
       0: bipush        21
       2: newarray       byte
       4: astore_0
       5: bipush        50
       7: newarray       byte
       9: astore_1
      10: bipush        30
      12: newarray       byte
      14: astore_2
      15: goto          0

因此,运行时内存分配必须相同。

让我们考虑一下Option 2:

对于第二次迭代,请考虑以下内容:

one= new byte [21];

当数组对象 new byte [21] 创建但尚未分配给 one 时,内存中将有两个对象:

  1. 在第一次迭代中创建并分配给的对象 one。此对象仍在范围内,因此不符合 GC.
  2. 的条件
  3. 刚刚创建但尚未分配给 one 的对象。此对象也在范围内,不符合 GC.
  4. 的条件

因此 Option 2 中的内存使用量将超过 Option 1,在类似情况下,在第一次迭代中创建的对象将超出范围,并且有资格进行垃圾回收。

所以 Option 1 在堆 space 使用方面更好!

下面是证明我观点的程序:

import org.junit.Assert;
import org.junit.Test;

public class VariableDeclarationTest {

    // this value may be increased or decreased as per system
    private static final int ARRAY_SIZE = 5400;

    @Test
    public void testDeclareVariableInsideLoop() {
        System.out.println("\n--------testDeclareVariableInsideLoop --------");

        boolean successFlag = false;
        for (int i = 1; i <= 3; i++) {
            System.out.println("iteration: " + i);
            Integer[][] arr = getLargeArray(ARRAY_SIZE); // declare inside loop
            System.out.println("Got an array of size: " + arr.length);
            successFlag = true;

        }
        Assert.assertEquals(true, successFlag);

    }

    @Test(expected = OutOfMemoryError.class)
    public void testDeclareVariableOutsideLoop() {
        System.out.println("\n---------testDeclareVariableOutsideLoop --------");
        Integer[][] arr = null; // declare outside loop
        for (int i = 1; i <= 3; i++) {
            System.out.println("iteration: " + i);
            arr = getLargeArray(ARRAY_SIZE);
            System.out.println("Got an array of size: " + arr.length);

        }

    }

    private Integer[][] getLargeArray(int size) {
        System.out.print("starts producing array....");
        Integer[][] arr = new Integer[size][size];
        for (int i = 0; i < arr.length; i++) {
            for (int j = 0; j < arr[i].length; j++) {
                arr[i][j] = size;
            }
        }
        System.out.println(" completed");
        return arr;
    }

}

ARRAY_SIZE 的值可能会根据系统的配置和负载而增加或减少。在某一时刻,可以看到在循环外声明变量的方法会抛出 OutOfMemoryError 但在内部声明变量的方法不会。

简短的回答是:Option 1 因为它的初始化方式。