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

Java 如何在无限循环中使用更少的堆空间?,java,loops,memory-leaks,heap-memory,heap-dump,Java,Loops,Memory Leaks,Heap Memory,Heap Dump,我的java应用程序的内存使用有问题。同时具有堆空间和非堆空间。现在,我专注于堆空间 我的应用程序是一个SocketServer,它通过DataInputStream获取输入。我以字节数组的形式读取信息。我每秒有不规则的输入量,但我们说的是每秒400字节到1000字节的空间,峰值可能会更高 由于我的程序是一个服务器,它在无休止的循环中等待输入。现在我遇到了一个问题,我的堆空间随着时间的推移不断增加,在5-10分钟内,它会增加0,5MB 我使用了多种监视器应用程序,比如jconsole和YourP

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

我的应用程序是一个SocketServer,它通过DataInputStream获取输入。我以字节数组的形式读取信息。我每秒有不规则的输入量,但我们说的是每秒400字节到1000字节的空间,峰值可能会更高

由于我的程序是一个服务器,它在无休止的循环中等待输入。现在我遇到了一个问题,我的堆空间随着时间的推移不断增加,在5-10分钟内,它会增加0,5MB

我使用了多种监视器应用程序,比如jconsole和YourProfiler。之后,我试图借助堆转储来解决这个问题,我使用jmap对堆转储进行处理,并使用Eclipse内存分析器进行分析

现在我的问题是,在这个示例代码中,哪个选项更好,或者说使用更少的堆空间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;
}
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;
}
选项2:

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;
}
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测试结果:

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;
}
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;
}

变量
one
two
three
只是引用:它们本身并不保存值,而是引用堆中存储实际数组对象的位置

因此,就分配的对象数量而言,这两种方法没有区别

选项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*#迭代)
数组对象

请注意,只有在不泄漏对数组的引用的情况下,才能使用这种方法,例如,将数组放入循环体外部的列表中


要证明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

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

变量
one
two
three
只是引用:它们本身并不保存值,而是引用堆中存储实际数组对象的位置

因此,就分配的对象数量而言,这两种方法没有区别

选项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*#迭代)
数组对象

请注意,只有在不泄漏对数组的引用的情况下,才能使用这种方法,例如,将数组放入循环体外部的列表中


要证明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

这样,运行时内存分配必须是相同的。

让我们考虑<代码>选项2 < /C>:< /P>

第二次迭代考虑如下:

1=新字节[21]

创建数组对象
新字节[21]
但未分配给
one
时,内存中将有两个对象:

  • 在第一次迭代中创建并指定给的对象
    一个
    。此对象仍在范围内,因此不符合
    GC
  • 刚刚创建但尚未分配给
    一个
    的对象。此对象也在范围内,不符合
    GC
  • 因此,
    选项2
    中的内存使用量将超过
    选项1
    中的内存使用量,在类似情况下,在第一次迭代中创建的对象将超出范围,并有资格进行垃圾收集

    因此,
    选项1
    在堆空间使用方面更好

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

    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;
        }
    
    }
    
    import org.junit.Assert;
    导入org.junit.Test;
    公共类VariableDeclarationTest{
    //该值可根据系统的不同而增加或减少
    私有静态最终整数数组_SIZE=5400;
    @试验
    public void testdeclarevariableindeloop(){
    System.out.println(“\n-------testdeclarevariableindeloop------”;
    布尔successFlag=false;
    
    对于(int i=1;i让我们考虑<代码>选项2 < /> >:

    第二次迭代考虑如下:

    one=新字节[21];

    创建数组对象
    新字节[21]
    但未分配给
    one
    时,内存中将有两个对象:

  • 在第一次迭代中创建并指定给的对象
    one
    。此对象仍在范围内,因此不符合
    GC
  • 刚刚创建但尚未分配给
    one
    的对象。此对象也在范围内,不符合
    GC
  • 因此,
    选项2
    中的内存使用量将超过
    选项1
    中的内存使用量,在类似情况下,在第一次迭代中创建的对象将超出范围,并有资格进行垃圾收集

    因此,
    选项1
    在堆空间使用方面更好

    F