简体   繁体   English

Java使用比预期更多的内存

[英]Java uses more memory than anticipated

Ok, so I try to do this little experiment in java. 好的,所以我尝试在Java中做这个小实验。 I want to fill up a queue with integers and see how long it takes. 我想用整数填充队列,看看需要多长时间。 Here goes: 开始:

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

OK, so I run this and get a 好,让我运行这个并得到一个

Hello World!
12396

About 12 secs, not bad for 50 million integers. 大约12秒,对于5000万个整数来说还不错。 But if I try to run it for 70 million integers I get: 但是,如果我尝试以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)

I also notice that it takes about 10 mins to come up with this message. 我还注意到,此消息大约需要10分钟。 Hmm so what if I give almost all my memory (8gigs) for the heap? 嗯,如果我将堆的几乎所有内存(8gig)都给我怎么办? So I run it for heap size of 7gigs but I still get the same error: 所以我以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)

I want to ask two things. 我想问两件事。 First, why does it take so long to come up with the error? 首先, 为什么要花这么长时间才能得出错误? Second, Why is all this memory not enough ? 第二, 为什么所有这些记忆都不够 If I run the same experiment for 300 million integers in C (with the glib g_queue) it will run (and in 10 secs no less! although it will slow down the computer alot) so the number of integers must not be at fault here. 如果我对C中的3亿个整数(使用glib g_queue)运行相同的实验,它将运行(并且在10秒之内!尽管会减慢计算机的运行速度),所以整数的数量一定不会有问题。 For the record, here is the C code: 作为记录,这是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);

}

I compile and get the result: 我编译并得到结果:

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

You can catch OutOfMemoryError with : 您可以使用以下命令捕获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); } 

in order to show when the OutOfMemoryError is thrown. 为了显示何时抛出OutOfMemoryError。

And launch your code with : 并使用以下代码启动代码:

java -Xmx4G javaQueueTest

in order to increase heap size for JVM 为了增加JVM的堆大小

As mentionned earlier, Java is much slower with Objects than C with primitive types ... 如前所述,Java使用对象时比使用原始类型的C要慢得多。

In your case, the GC struggles as it assumes that at least some objects will be short lived. 在您的情况下,GC苦苦挣扎,因为它假设至少某些对象是短命的。 In your case all objects are long lived, this adds a significant overhead to managing this data. 在您的情况下,所有对象都是长期存在的,这会增加管理该数据的开销。

If you use -Xmx7g -Xms7g -verbose:gc and N = 150000000 you get an output like 如果使用-Xmx7g -Xms7g -verbose:gcN = 150000000则会得到类似以下的输出

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

int is a primitive in Java (4 -bytes), while Integer is the wrapper. int是Java(4字节)中的基元,而Integer是包装器。 This wrapper need a reference to it and a header and padding and the result is that an Integer and its reference uses 20 bytes per value. 该包装器需要对其的引用以及标头和填充,结果是Integer及其引用每个值使用20个字节。

The solution is to not queue up some many values at once. 解决方案是不要一次将许多值排队。 You can use a Supplier to provide new values on demand, avoiding the need to create the queue in the first place. 您可以使用供应商按需提供新值,而无需首先创建队列。

Even so, with 7 GB heap you should be able to create a ArrayQueue of 200 M or more. 即使这样,使用7 GB的堆,您仍应该能够创建200 M或更大的ArrayQueue。

My rough thoughts about your questions: 关于您的问题,我的粗略想法是:

First, why does it take so long to come up with the error? 首先,为什么要花这么长时间才能得出错误?

As gimpycpu in his comment stated, java does not start with full memory acquisition of your RAM. 正如gimpycpu在他的评论中所言,java并不是从内存的全内存获取开始的。 If you want so (and you have a 64 bit VM for greater amount of RAM), you can add the options -Xmx8g and -Xms8g at VM startup time to ensure that the VM gots 8 gigabyte of RAM and the -Xms means that it will also prepare the RAM for usage instead of just saying that it can use it. 如果需要(您有一个64位VM用于更大的RAM),则可以在VM启动时添加选项-Xmx8g和-Xms8g以确保VM获得8 GB的RAM,并且-Xms表示还将准备使用RAM,而不仅仅是说它可以使用。 This will reduce the runtime significantly. 这将大大减少运行时间。 Also as already mentioned, Java integer boxing is quite overhead. 同样如前所述,Java整数装箱是相当大的开销。

Why is all this memory not enough? 为什么所有这些记忆还不够?

Java introduces for every object a little bit of memory overhead, because the JVM uses Integer references in the ArrayDeque datastructur in comparision to just 4 byte plain integers due to boxing. Java为每个对象引入了一点内存开销,因为由于装箱,JVM将ArrayDeque数据结构中的Integer引用与仅4字节纯整数进行比较。 So you have to calulate about 20 byte for every integer. 因此,您必须为每个整数计算大约20个字节。
You can try to use an int[] instead of the ArrayDeque: 您可以尝试使用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);
    }
}

This will be ultra fast and due the usage of plain arrays. 这将是超快的,并且由于使用了普通数组。 On my system I am under one second for every run! 在我的系统上,每次运行我都不到一秒钟!

First, why does it take so long to come up with the error? 首先,为什么要花这么长时间才能得出错误?

This looks like a classic example of a GC "death spiral". 这看起来像是GC“死亡螺旋”的经典示例。 Basically what happens is that the JVM does full GCs repeatedly, reclaiming less and less space each time. 基本上,发生的事情是JVM重复执行完整的GC,每次都回收越来越少的空间。 Towards the end, the JVM spends more time running the GC than doing "useful" work. 最终,JVM花费在GC上的时间要多于“有用的”工作。 Finally it gives up. 最后它放弃了。

If you are experiencing this, the solution is to configure a GC Overhead Limit as described here: 如果遇到这种情况,解决方案是按如下所述配置GC开销限制:

(Java 8 configures a GC overhead limit by default. But you are apparently using an older version of Java ... judging from the exception message.) (默认情况下,Java 8配置GC开销限制。但是显然您使用的是Java的旧版本...从异常消息中判断。)

Second, Why is all this memory not enough? 第二,为什么所有这些记忆还不够?

See @Peter Lawrey's explanation. 请参阅@Peter Lawrey的说明。

The workaround is to find or implement a queue class that doesn't use generics. 解决方法是查找或实现不使用泛型的队列类。 Unfortunately, that class will not be compatible with the standard Deque API. 不幸的是,该类将与标准的Deque API不兼容。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM