好的,所以我尝试在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
答案 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:gc
和N = 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不兼容。