Java使用的内存比预期的多

时间:2014-12-29 10:33:04

标签: java testing memory

好的,所以我尝试在java中进行这个小实验。我想用整数填充队列,看看需要多长时间。这是:

import java.io.*;
import java.util.*;

class javaQueueTest {
public static void main(String args[]){
    System.out.println("Hello World!");
    long startTime = System.currentTimeMillis();
    int i;
    int N = 50000000;

    ArrayDeque<Integer> Q = new ArrayDeque<Integer>(N);
    for (i = 0;i < N; i = i+1){
        Q.add(i);
    }
    long endTime   = System.currentTimeMillis();
    long totalTime = endTime - startTime;
    System.out.println(totalTime);
}
}

好的,所以我运行它并得到一个

Hello World!
12396

大约12秒,对于5000万个整数来说也不错。但如果我尝试以7000万个整数运行它,我会得到:

Hello World!
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.lang.Integer.valueOf(Integer.java:642)
    at javaQueueTest.main(javaQueueTest.java:14)

我还注意到这个消息需要大约10分钟。嗯,如果我几乎把所有的内存(8gigs)都用于堆呢?所以我运行它的堆大小为7gigs但我仍然得到相同的错误:

javac javaQueueTest.java
java -cp . javaQueueTest -Xmx7g
Hello World!
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.lang.Integer.valueOf(Integer.java:642)
    at javaQueueTest.main(javaQueueTest.java:14)

我想问两件事。首先, 为什么要花这么长时间才能得出错误? 其次, 为什么所有这些内存都不够 ?如果我在C中运行3亿个整数的相同实验(使用glib g_queue)它将运行(并且在10秒内不会少!虽然它会减慢计算机的速度)所以整数的数量一定不会有问题。记录中,这是C代码:

#include<stdlib.h>
#include<stdio.h>
#include<math.h>
#include<glib.h>
#include<time.h>

int main(){
clock_t begin,end;
double time_spent;
GQueue *Q;

begin = clock();
Q = g_queue_new();
g_queue_init(Q);
int N = 300000000;
int i;
for (i = 0; i < N; i = i+1){
    g_queue_push_tail(Q,GINT_TO_POINTER(i));
}
end = clock();
time_spent = (double)(end - begin) / CLOCKS_PER_SEC;
printf("elapsed time: %f \n",time_spent);

}

我编译并得到结果:

gcc cQueueTest.c `pkg-config --cflags --libs glib-2.0 gsl ` -o cQueueTest
~/Desktop/Software Development/Tests $ ./cQueueTest 
elapsed time: 13.340000

4 个答案:

答案 0 :(得分:0)

您可以使用以下命令捕获OutOfMemoryError:

try{
    ArrayDeque<Integer> Q = new ArrayDeque<Integer>(N);
    for (i = 0;i < N; i = i+1){
        Q.add(i);
    }
}
catch(OutOfMemoryError e){
    Q=null;
    System.gc();
    System.err.println("OutOfMemoryError: "+i);
}

以显示何时抛出OutOfMemoryError。

使用以下命令启动您的代码:

java -Xmx4G javaQueueTest

为了增加JVM的堆大小

如前所述,使用对象的Java比使用原始类型的C要慢得多......

答案 1 :(得分:0)

在你的情况下,GC挣扎,因为它假设至少有些物体是短暂的。在您的情况下,所有对象都是长期存在的,这会增加管理此数据的巨大开销。

如果您使用-Xmx7g -Xms7g -verbose:gcN = 150000000,则会获得类似

的输出
Hello World!
[GC (Allocation Failure)  1835008K->1615280K(7034368K), 3.8370127 secs]
5327

int是Java中的原语(4字节),而Integer是包装器。这个包装器需要对它的引用以及头和填充,结果是Integer及其引用每个值使用20个字节。

解决方案是不要一次排队多个值。您可以使用供应商按需提供新值,从而无需首先创建队列。

即便如此,使用7 GB堆时,您应该能够创建200 M或更大的ArrayQueue。

答案 2 :(得分:0)

我对你的问题的粗略想法:

  

首先,为什么要花这么长时间才能得出错误?

正如gimpycpu在他的评论中指出的那样,java并不是从你的RAM的完全内存获取开始的。如果你想这样(你有一个64位虚拟机可以获得更大的RAM),你可以在虚拟机启动时添加选项-Xmx8g和-Xms8g,以确保虚拟机获得8千兆字节的RAM,-Xms意味着它还将准备RAM用于使用,而不是仅仅说它可以使用它。这将显着减少运行时间。同样如前所述,Java整数装箱是非常开销的。

  

为什么所有这些记忆都不够?

Java为每个对象引入了一点内存开销,因为JVM使用ArrayDeque数据结构中的Integer引用,因为拳击只比较4字节的普通整数。所以你必须为每个整数计算大约20个字节 您可以尝试使用int []而不是ArrayDeque:

import java.io.*;
import java.util.*;

class javaQueueTest {
    public static void main(args){
        System.out.println("Hello World!");
        long startTime = System.currentTimeMillis();
        int i;
        int N = 50000000;
        int[] a = new int[N];
        for (i = 0;i < N; i = i+1){
            a[i] = 0;
        }
        long endTime   = System.currentTimeMillis();
        long totalTime = endTime - startTime;
        System.out.println(totalTime);
    }
}

由于普通阵列的使用,这将是超快的。 在我的系统上,每次跑步都不到一秒!

答案 3 :(得分:0)

  

首先,为什么要花这么长时间才能得出错误?

这看起来像GC“死亡螺旋”的典型例子。基本上发生的事情是JVM重复执行完整的GC,每次回收的空间越来越少。接近最后,JVM花费更多时间来运行GC而不是做“有用”的工作。最后它放弃了。

如果您遇到这种情况,解决方法是按照此处所述配置GC开销限额:

(Java 8默认配置GC开销限制。但是你显然使用旧版本的Java ......从异常消息判断。)

  

第二,为什么所有这些记忆都不够?

参见@Peter Lawrey的解释。

解决方法是查找或实现不使用泛型的队列类。不幸的是,该类与标准Deque API不兼容。