Java heap space: Hashmap, ArrayList



我想处理一个文本文件(大约400 MB),以便根据每行中给出的数据创建递归父子结构。必须为自上而下的导航准备数据(输入:父级,输出:所有子级和子级)。例如,要读取的行数:(、id1、id2、和id3)

132142086;1.21322528589;132528599
132142087;1.31322528589;132528599
132142088;1.01322528589;132528599
323442444;1.0132142088;132528599
454345434;1.0323442444;132528599

132528589:是132142086132142087132142088的父级
132142088:是323442444的父级
323442444:是454345434的父级

给定:操作系统windows xp,32位,2GB可用内存和-Xmx1024m以下是我准备数据的方法:

HashMap<String,ArrayList<String>> hMap=new HashMap<String,ArrayList<String>>();
  while ((myReader = bReader.readLine()) != null) 
          {
             String [] tmpObj=myReader.split(delimiter);
                   String valuesArrayS=tmpObj[0]+";"+tmpObj[1]+";"+tmpObj[2]+";"+tmpObj[3]+";"+tmpObj[4];
                        ArrayList<String> valuesArray=new ArrayList<String>();
                        //case of same key
                        if(hMap.containsKey(tmpObj[3]))
                            {
                            valuesArray=(ArrayList<String>)(hMap.get(tmpObj[3])).clone();
                            }
                        valuesArray.add(valuesArrayS);
                        hMap.put(tmpObj[3],valuesArray);
                        tmpObj=null;
                        valuesArray=null;
                        }
return hMap;

之后,我使用递归函数:

HashMap<String,ArrayList<String>> getChildren(input parent)

用于创建所需的数据结构。计划是使用函数getChildren为多个线程提供hMap(只读)。
我用90MB的输入文件测试了这个程序,它似乎工作正常。但是,使用超过380 MB的真实文件运行它会导致:
线程"main"java.lang.OutOfMemoryError中出现异常:java堆空间
我需要一些内存资源管理方面的帮助

从"非常简单的方法"的角度来看:根据你的问题陈述,你不需要保留id1、id2或id3。假设是这样的话,用HashMap<Integer, ArrayList<Integer>>替换HashMap<String, ArrayList<String>>怎么样?可以使用Integer.parseInt()进行字符串到int的转换,Integer应该始终小于相应的string。

其他建议:如果你不在乎重复,用HashSet替换你的ArrayList

根据outofBounds的回答,你不需要每次都克隆一个ArrayList来添加一个项目。

按照其他人的建议,检查一下是否增加了你的内存。此外,您可以按照Sbodd和其他人的建议,更好地将数据存储在表中。

然而,您可能正在与内存碎片冲突。哈希映射使用数组。大散列映射使用大数组。您没有指定哈希映射的大小,所以每次它决定需要更大时,都会丢弃旧数组并分配一个新数组。过一段时间,您的内存会被丢弃的哈希表数组填满,并且您会得到OutOfMemoryException,尽管从技术上讲您有足够的可用内存。(90%的内存可以使用,但碎片太小,无法使用。)

垃圾收集器(GC)将持续工作,将所有这些空闲位组合成足够大的块来使用。如果你的程序运行得足够慢,你就不会有问题,但你的程序正在全速运行,GC会落后的。如果GC不能以足够快的速度组装足够大的空闲块,它将抛出异常;内存的存在并不能阻止它

如果你知道你的哈希图必须有多大,我会提前设置大小。即使大小不完全正确,它也可以在不增加堆大小的情况下解决内存问题,并且肯定会使程序运行得更快(或者与文件读取允许的速度一样快——使用文件缓冲区)。

如果你真的不知道你的桌子有多大,可以使用TreeMap。它有点慢,但不会分配巨大的数组,因此对GC更友好。我发现它们更加灵活和有用。您甚至可以查看ConcurrentSkipTreeMap,它比TreeMap慢,但允许您同时添加、读取和删除多个线程。

但你最好的选择是:

hMap = new HashMap<String,ArrayList<String>>( 10000000 );

在While循环中,u可以减少一些空间,类似于

String [] tmpObj=myReader.split(delimiter);
// String = String + String takes more Space than String.format(...)
//String valuesArrayS=tmpObj[0]+";"+tmpObj[1]+";"+tmpObj[2]+";"+tmpObj[3]+";"+tmpObj[4];
// Just Adding if thers is no List for a Key
if(!hMap.containsKey(tmpObj[3]){
    hMap.put(tmpObj[3], new ArrayList<String>());
}
// Gettin the list from the Map and adding the new stuff
List<String> values = hMap.get(tmpObj[3]);
values.add(String.format("%s;%s;%s;%s;%s",tmpObj[0], tmpObj[1], tmpObj[2], tmpObj[3], tmpObj[4]));

无需克隆列表

您实际上是在测试1GB内存的极限。

你可以:

  1. 增加堆空间。32位窗口会将您的容量限制在1.5GB左右,但您仍有更多的活动空间,这可能足以让您脱颖而出
  2. 构建某种类型的预处理器实用程序,以您知道的大小对文件进行预分区,并一次对其进行操作,也许是在等级上
  3. 试着重新组织你的课程。在java中,字符串是不可变的拆分字符串并与正在创建的+运算符连接新字符串一直存在(10种情况中有9种情况不重要,但在您使用非常有限的资源的情况下,这可能会有所不同)

作为一个不太有用的注释。这里真正的问题是,你没有资源来处理这项任务,而优化只会让你走这么远。这就像在问如何更好地用花园泥铲在山上挖隧道一样。真正的答案可能是你不想听到的,那就是扔掉铲子,投资一些工业设备

第二个更有用的注意事项(如果你和我一样,也很有趣)是,你可以尝试将jVisualVM连接到你的应用程序上,并试图了解你的堆的去向,或者使用jhat和-XX:+HeapDumpOnOutOfMemoryErrorjvm标志来查看崩溃时堆发生了什么。

最新更新