如何在无限循环中使用更少的堆空间?

时间:2016-07-15 07:53:59

标签: java loops memory-leaks heap-memory heap-dump

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

我的应用程序是一个SocketServer,它通过DataInputStream获取Input。我在信息中读取字节数组。我每秒都得到一个不规则的输入量,但我们说的是每秒400字节到1.000字节的空间,峰值可以更高。

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

我使用了多个监控应用,例如jconsole和YourProfiler。之后我试着在Heap Dumps的帮助下弄清楚,我用jmap进行了解析并用Eclipse Memory Analyzer进行分析。

现在我的问题是,在这个示例代码中,哪个选项更好或更少使用更少的堆空间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会删除它们并在下一个循环中创建一个新循环。所以我猜应该没有内存泄漏?

在第二个选项中,三个变量在循环外声明,因此它们将在整个时间内保持活动状态。在循环中,这些对象的引用会发生变化,因此不会引用旧内容,这意味着它会被GC分别删除。

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

提前感谢您的帮助!

JUnit测试结果:

enter image description here

3 个答案:

答案 0 :(得分:5)

变量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

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

答案 1 :(得分:0)

让我们考虑<div class="grid"> @foreach (var item in Model) { // converting to string var base64 = Convert.ToBase64String(item); imgsrc = string.Format("data:image/jpg;base64,{0}", base64); <div class="grid__item" data-size="1280x857"> <img src="@imgsrc" style="max-width:100px;max-height:100px" /> </div> } <div class="description description--grid"> </div> </div>

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

Option 2

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

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

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

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

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

    Option 1

    ARRAY_SIZE的值可以根据系统的配置和负载增加和减少。有一次,可以看到变量在循环外声明的方法抛出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; } } 但是在里面声明变量的方法却没有。

答案 2 :(得分:-1)

简短回答是:Option 1 因为它的启动方式。