首页   注册   登录
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX  ›  程序员

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

  •  
  •   buptlee · 2015-01-12 17:07:14 +08:00 · 2589 次点击
    这是一个创建于 1646 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我的程序要读入一个数据文件,文件的每一行是这样的:
    [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");
    
    25 回复  |  直到 2015-01-15 14:19:43 +08:00
        1
    kaneg   2015-01-12 17:20:53 +08:00   ♥ 1
    以下是我的一点看法:
    1. 你这里用的是Float对象,并不是float基本类型,基本类型是4个byte,而一个Float对象则要大得多。每个对象都与一个引用,64位jvm,每个应用要8个字节,加上float本身的存储,就已经是3倍了。索尼你不要用Float,直接用float
    2. 其次你使用了ArrayList, 你只初始化了一个维度的长度,另一个维度的没有,那它就需要不断动态调整,这样也有额外的内存消耗。这里你既然已经知道了长度,就应该用数组,不要用ArrayList
        2
    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比数组低效了呢?
        3
    buptlee   2015-01-12 17:30:38 +08:00
    @kaneg 我其实也是想直接用float的,但是在声明的时候,
    ArrayList<float> K_L_item = new ArrayList<float>();会出错,必须要包装类型才可以。
        4
    kaneg   2015-01-12 17:32:07 +08:00   ♥ 1
    基本类型是不能用泛型的,所以ArrayList<float>不支持。
    数组是Java原生提供的数据结构,ArrayList是一个普通的类,不考虑性能的话,ArrayList是很好用,但考虑性能最好老老实实用数组,除非你不在乎内存消耗
    float[][] data = new float[12000][12000];
        5
    songco   2015-01-12 17:33:13 +08:00   ♥ 1
    我记得java有8 byte的object header, 另外你用Float的话, 还有reference的开销. 还有内存对齐的padding, 我记得java对象是按8 byte对齐的. 这些加起来估计比你算的4 byte多很多.
    具体的情况还是弄个heap dump看看吧.

    这种原始数据如果需要全部加载, 建议用primitive type的多维数组, 或者用c之类的写, 当然如果能根据业务优化一下不要全部加载进来就更好了.
        6
    buptlee   2015-01-12 17:35:07 +08:00
    @kaneg 我重新写一下程序。用惯了ArrayList,对原生态数组表示有点生疏了,thanks,it's so kind of you.
        7
    buptlee   2015-01-12 17:37:15 +08:00
    @songco 嗯,总是被java的内存折磨,看来primitive type数组才是真爱,不该贪图方便一股脑ArrayList。谢谢你。
        8
    mfaner   2015-01-12 17:45:52 +08:00   ♥ 1
    ArrayList<Float> K_L_item = new ArrayList<Float>();
    这里也要指定capacity,觉得应该会影响很大
        9
    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/)
        10
    songco   2015-01-12 18:18:20 +08:00 via iPhone   ♥ 1
    @mfaner 增长策略是每次增长一半,然后拷贝……
        11
    msg7086   2015-01-12 20:53:56 +08:00 via iPhone   ♥ 1
    典型的内存密集型操作,托管语言的短板之一啊。
    用C系应该会好得多。
        12
    mfaner   2015-01-12 22:20:19 +08:00   ♥ 1
    再来补充下,split方法是正则匹配,而且内部也是一个无参构造的ArrayList。
        13
    coolcfan   2015-01-12 23:05:17 +08:00   ♥ 1
    token那块老老实实indexof或者找个高效的库处理吧。。。
        14
    icespace   2015-01-13 09:32:01 +08:00   ♥ 1
    有两个思路值得考虑
    1.使用内存型数据库管理数据
    2.使用内存映射文件
        15
    thinkmore   2015-01-13 09:34:09 +08:00   ♥ 1
    数据量过大了,就算你使用基本类型数组提高也不大,当然是肯定有提升的,毕竟ArrayList内部就是使用的基本类型数组罢了,建议你可以一次性读1W行(不一定是1W,找一个比较合理的数据)
        16
    buptlee   2015-01-13 11:16:23 +08:00
    @icespace redis算是您说的第一个条中的一种吗?
        17
    buptlee   2015-01-13 11:17:29 +08:00
    @thinkmore 不可以呢,我需要全局操作,不能只读一部分数据,业务使然。thanks very much!
        18
    buptlee   2015-01-13 11:17:58 +08:00
    @icespace 内存映射文件是指什么呢,能不能具体点?
        19
    buptlee   2015-01-13 11:22:25 +08:00
    @msg7086 确实是这样。就算是我用了数组去做,还是要消耗接近3G的内存,不过好歹能把所有数据读进去了。
        20
    buptlee   2015-01-13 11:23:08 +08:00
    @coolcfan 比如什么高效的库呢?学习一下。
        21
    wudikua   2015-01-13 14:08:15 +08:00
    你想想为什么array list可以不用管数组的下标限制直接可以无限的add而不抛出out of bound就知道了
        22
    icespace   2015-01-14 10:18:01 +08:00
    @buptlee MappedByteBuffer , 即利用file chanel(NIO)将文件部分或全部映射到内存中,用来处理大文件。这样,你就可以利用操作系统的功能来间接操作文件,而且避免了文件操作错误。需要注意的是,如果处理4G以上的大文件,你需要使用64位JVM来运行。卤煮请移步http://www.linuxidc.com/Linux/2013-11/92895.htm 。所以这个基本上就是你要的。
        23
    icespace   2015-01-14 10:23:06 +08:00
    @buptlee 另外的一个思路是使用嵌入型数据库,将文件结构化的导入嵌入型数据库后,数据的存储与缓存就交由它来处理,典型的如BerkeleyDB。这个办法比内存映射文件更加有效是因为你的数据是结构化的,这样你就可以充分利用数据的排序、查找、过滤,其处理速度将会远远超过你自己写代码,而且极大减少debug的时间。你不在需要自己组织庞大的数组结构,也不在需要自己去hash。我想唯一的缺点可能就是程序包会稍微大一点。卤煮,我给你的这两条建议,你还满意么?
        24
    buptlee   2015-01-14 21:58:41 +08:00
    满意到哭啊,多谢大神。
    目前在钻研第一个思路。
    第二个思路感觉有点复杂,先不尝试了。
    thanks so much
        25
    icespace   2015-01-15 14:19:43 +08:00
    @buptlee 我建议你使用第二个思路,第一个虽然解决了文件加载的问题,但是你仍然要在内存中构建巨大的数据结构,实际上并未解决问题的根源。我建议你用第二个,嵌入型数据库不用安装配置,使用同一个jvm,因此相当简单,而且数据库的使用应该是最常用的场景。
    关于   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   2519 人在线   最高记录 5043   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.3 · 25ms · UTC 00:40 · PVG 08:40 · LAX 17:40 · JFK 20:40
    ♥ Do have faith in what you're doing.