熟悉 java 内存机制的同学们来帮我瞅瞅我的内存问题吧。

2015-01-12 17:07:14 +08:00
 buptlee

我的程序要读入一个数据文件,文件的每一行是这样的:
[1001,0.0,2.846335314101648,0.2385266331350405,0.6889106550987336,0.6265634872937542,0.7916442990777731...]

每一行是由逗号分隔的11063个字段,我把读到的每一个字段转换成了float类型。每一行存到一个 ArrayList<Float>里面,一共是11063行,也就是说我存下了一个11063*11063的矩阵,矩阵的每个元素是float类型,所以总的大小应该是11063*11063*4/(1024*1024)=466M

程序读入到1万行的时候就卡住了,查看任务管理器显示内存占用量是3.4G,并且还发生了一个小插曲,当读到4000行的时候卡了20秒,按我的理解可能是在这里数组重新申请了内存,可是有有点疑惑,因为我在程序的最开始是初始化了K_L数组的:
ArrayList<ArrayList<Float>> K_L = new ArrayList<ArrayList<Float>>(12000);

按理来说不会发生类似于rehash这种动作的啊。这只是一个小插曲,我的问题是,为什么计算得到的内存是466M,而实际上用了3.4G都没有读完,并且最后停在了10000行处,就算是有额外的开销,也不至于这么大啊。我的程序非常简单,在数据结构和算法上应该是不能更精简了。我想问问同学们关于我的内存问题。还有就是大家在将这种较大的数据读入到内存中时,有什么好的方案吗,比如redis?我不太了解,只是听人提起过。大家分享下吧,thanks。代码如下。

String each_line = null;
    while((each_line = K_L_file_handler.readLine())!=null){
        String tokens [] = each_line.split(",");
        ArrayList<Float> K_L_item = new ArrayList<Float>();
        for(int i = 0 ; i < tokens.length ; i++){ 
            K_L_item.add( Float.parseFloat(tokens[i]));
        }
        K_L.add(K_L_item);
        }
    System.out.println("finish initialing the  Kullback–Leibler divergence");
3548 次点击
所在节点    程序员
25 条回复
kaneg
2015-01-12 17:20:53 +08:00
以下是我的一点看法:
1. 你这里用的是Float对象,并不是float基本类型,基本类型是4个byte,而一个Float对象则要大得多。每个对象都与一个引用,64位jvm,每个应用要8个字节,加上float本身的存储,就已经是3倍了。索尼你不要用Float,直接用float
2. 其次你使用了ArrayList, 你只初始化了一个维度的长度,另一个维度的没有,那它就需要不断动态调整,这样也有额外的内存消耗。这里你既然已经知道了长度,就应该用数组,不要用ArrayList
buptlee
2015-01-12 17:27:14 +08:00
@kaneg 可是ArrayList<float> K_L_item = new ArrayList<float>();
ArrayList<ArrayList<float>> K_L = new ArrayList<ArrayList<float>>(12000);
都不被允许啊。
还有就是数组和ArrayList有本质的区别吗,或者说,什么原因使得ArrayList比数组低效了呢?
buptlee
2015-01-12 17:30:38 +08:00
@kaneg 我其实也是想直接用float的,但是在声明的时候,
ArrayList<float> K_L_item = new ArrayList<float>();会出错,必须要包装类型才可以。
kaneg
2015-01-12 17:32:07 +08:00
基本类型是不能用泛型的,所以ArrayList<float>不支持。
数组是Java原生提供的数据结构,ArrayList是一个普通的类,不考虑性能的话,ArrayList是很好用,但考虑性能最好老老实实用数组,除非你不在乎内存消耗
float[][] data = new float[12000][12000];
songco
2015-01-12 17:33:13 +08:00
我记得java有8 byte的object header, 另外你用Float的话, 还有reference的开销. 还有内存对齐的padding, 我记得java对象是按8 byte对齐的. 这些加起来估计比你算的4 byte多很多.
具体的情况还是弄个heap dump看看吧.

这种原始数据如果需要全部加载, 建议用primitive type的多维数组, 或者用c之类的写, 当然如果能根据业务优化一下不要全部加载进来就更好了.
buptlee
2015-01-12 17:35:07 +08:00
@kaneg 我重新写一下程序。用惯了ArrayList,对原生态数组表示有点生疏了,thanks,it's so kind of you.
buptlee
2015-01-12 17:37:15 +08:00
@songco 嗯,总是被java的内存折磨,看来primitive type数组才是真爱,不该贪图方便一股脑ArrayList。谢谢你。
mfaner
2015-01-12 17:45:52 +08:00
ArrayList<Float> K_L_item = new ArrayList<Float>();
这里也要指定capacity,觉得应该会影响很大
ericson
2015-01-12 18:17:42 +08:00
也有支持primitve type的高性能集合库:

* [fastutil](http://fastutil.di.unimi.it/)
* [OpenHFT](https://github.com/OpenHFT/Koloboke)
* [hppc](http://labs.carrotsearch.com/hppc.html)
* [trove](http://trove.starlight-systems.com/)
songco
2015-01-12 18:18:20 +08:00
@mfaner 增长策略是每次增长一半,然后拷贝……
msg7086
2015-01-12 20:53:56 +08:00
典型的内存密集型操作,托管语言的短板之一啊。
用C系应该会好得多。
mfaner
2015-01-12 22:20:19 +08:00
再来补充下,split方法是正则匹配,而且内部也是一个无参构造的ArrayList。
coolcfan
2015-01-12 23:05:17 +08:00
token那块老老实实indexof或者找个高效的库处理吧。。。
icespace
2015-01-13 09:32:01 +08:00
有两个思路值得考虑
1.使用内存型数据库管理数据
2.使用内存映射文件
thinkmore
2015-01-13 09:34:09 +08:00
数据量过大了,就算你使用基本类型数组提高也不大,当然是肯定有提升的,毕竟ArrayList内部就是使用的基本类型数组罢了,建议你可以一次性读1W行(不一定是1W,找一个比较合理的数据)
buptlee
2015-01-13 11:16:23 +08:00
@icespace redis算是您说的第一个条中的一种吗?
buptlee
2015-01-13 11:17:29 +08:00
@thinkmore 不可以呢,我需要全局操作,不能只读一部分数据,业务使然。thanks very much!
buptlee
2015-01-13 11:17:58 +08:00
@icespace 内存映射文件是指什么呢,能不能具体点?
buptlee
2015-01-13 11:22:25 +08:00
@msg7086 确实是这样。就算是我用了数组去做,还是要消耗接近3G的内存,不过好歹能把所有数据读进去了。
buptlee
2015-01-13 11:23:08 +08:00
@coolcfan 比如什么高效的库呢?学习一下。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/161465

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX