我目前正试图在其中一个在线编程竞赛中解决问题。该比赛的限制为64兆字节。
我用Java编写了一个程序,它在类声明中有一段字段,其作用如下:
private int[] sizes = new int[1024]; // 4096 bytes
private boolean[][] compat = new boolean[1024][1024]; // 128 kb
private boolean[][] compat2 = new boolean[1024][1024]; // 128 kb
private long[][][] dp = new long[29000][51][2]; // About 3*8 = 24 megabytes
private int [][] masks = new int[29000][2]; // About 240 kb
private int avail = 0;
private int avail2 = 0;
private int[] positions = new int[500000]; // About 2 megabytes
private int[][] ranges = new int[29000][2]; // About 240 kb
private int[][] maskToPos = new int[1024][1024]; // About 4 megabytes
private int[][][] init = new int[29000][51][2]; // About 3*4 = 12 megabytes
现在,该类只有一个主程序,并且在其中有一些循环,没有声明任何其他数组(只是一些变量来迭代循环)。但是,然后我尝试使用密钥-Xmx64m在本地计算机上运行此代码,我有一个OutOfMemoryError。它只能使用密钥-Xmx128m执行。
我也尝试过在线服务器,它也给出了同样的错误,并且还提供了我的程序使用的大约148460 kb的其他信息。
但为什么这么多?据我可以从上面的片段计算,它只应该使用大约40兆字节。评论中的这个计算有问题吗?
答案 0 :(得分:10)
这两个是最大的杀手:
private long[][][] dp = new long[29000][51][2]; // About 3*8 = 24 megabytes
private int[][][] init = new int[29000][51][2]; // About 3*4 = 12 megabytes
看第二个,例如......不是12兆字节。您有29000个int[][]
个对象,每个包含对51个int[]
个对象的引用,每个对象包含2个整数。
假设数组本身的32位引用大小和16字节开销(长度+公共对象开销),这意味着int[][]
对象的大小各为51 * 4 + 16 = 220字节,然后int[]
个对象的大小均为24个字节。但是你有29000 * 51的24字节对象 - 本身只有35MB ......然后是29000 int[][]
个对象,这是另外6MB ...(然后是顶层数组本身,但那只是大约120K。)
基本上,您需要记住Java没有多维数组:它有数组数组,每个数组都是一个对象,具有单独的开销。我建议你可以使用:
private int[] init = new int[29000 * 51 * 2];
而是,并自己制定适当的抵消。 (同上dp,更糟糕的是long
值,而不是int
值,使29000 * 51阵列中的每一个至少需要32个字节而不是24个。)
即使只是颠倒处理维度的顺序,帮助:
private long[][][] dp = new long[2][51][29000];
private int[][][] init = new int[2][51][29000];
现在,对于每个变量,都有一个顶级数组数组,2个数组数组和102个long
或int
数组。这相当于 lot 开销较少。
您的其他计算也不正确,但我认为这两个数组阵列是最差的。
答案 1 :(得分:2)
问题是Java中的多维数组不是真正的多维数组;如果是,那么Java将支持[x,y]表示法。但事实并非如此。因为Java中的多维数组是作为数组的数组实现的。所以,new boolean[1024][1024]
实际上是1024个数组对象,每个对象包含1024个布尔值。 (每个1KB。)
我不记得哪个维度是主要的,哪个维度是次要的,但从你的程序内存不足的事实来看,第一个维度可能是主要维度。因此,new long[29000][51][2]
是29000 * 51 = 1479000个数组对象,每个对象包含2个长值。所以,有了这么多的对象,考虑到每个对象的开销,就算了吧!
答案 2 :(得分:1)
如上所述,long[29000][51][2]
需要超过24兆字节。您可以尝试通过将最大维度移动到数组末尾来减少内存量,如下所示:
private long[][][] dp = new long[51][2][29000];
这可能足以让您的节目在节目竞赛中吱吱作响。
答案 3 :(得分:1)
一个小建议:我会尝试让你的所有声明“最终”。大数组会导致内存分配问题,因为不仅必须找到空间,还必须找到连续空间。 Java可以移动东西以腾出空间,但是如果它需要太长时间,即使理论上空间可用,它也会抛出内存异常。你似乎是通过预先抓住你所有的记忆并保持它直到程序结束来躲避这个问题。使用“final”会让JVM知道你对此很认真,也许让它以一种帮助你的方式分配空间。
这可能对JVM没有帮助。我发现Java在过去几年变得非常聪明,它可能不需要你告诉它什么是最终的,什么不是。然而,需要告诉人们做。使用“final”将使您和其他任何人更改代码时不会意外地重新分配空间,例如在代码中的其他地方使用positions = new int[500010];
这样的语句并压倒JVM /垃圾收集器。