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

大家来帮我看看这个 java 内存问题吧,我快崩溃了。

  •  
  •   buptlee ·
    Xiaoxin2009 · 2014-08-31 21:53:55 +08:00 · 6233 次点击
    这是一个创建于 3517 天前的主题,其中的信息可能已经有所发展或是发生改变。
    程序的开始是读入一个文件里的数据,数据的格式是:
    每一行为三个字段,例如:
    2,3,0
    并且1-50行的第一个数字相同,51-100行的第一个数字相同,以此类推。
    一共是1.2亿行。我把它读到一个字典里面。我用的数据结构是:HashMap<Integer,LinkedList<Node>>
    键是每一行的第一个数字,值是一个LinkedList,Node有两个字段:
    Node{
    int ID;
    int distance;
    }
    分别是每一行的第二个数字和第三个数字。也就是说每一行会产生一个node。
    然后每50行放入一个LinkedList,再put进hashmap里面。我估算了一下,需要的内存是:
    0.12G*(4+4+4)=1.44G
    4是int类型占的字节数。加上node封装,linkedlist的使用可能会需要一些额外的内存,我给java虚拟机分配了6G的内存上线。
    程序运行的时候,发现开始读入的速度还是很快的,内存涨的很快,但cpu一直维持在较低水平,大概40-50%,但是当读入到2300万行的时候就卡主了,多次运行到这时都停住了,任务管理器显示java程序只占用了2.3G的内存,但很快cpu使用率达到了100%。
    我想问下大家,这是什么原因呢,是因为我使用了Node封装和LinkedList导致需要更多的内存吗。需要多多少呢。而且为什么我为这个java程序分配了6g的内存,就算是内存不足,也不应该卡在2.3G这个地方啊。难道是我分配内存的方法不对?大家都是怎么给某个java程序单独分配内存的?
    拜谢各位啊。
    第 1 条附言  ·  2014-09-01 09:34:19 +08:00
    public HashMap<Integer ,LinkedList<Node> > getDistMatrix(String fileName,HashMap<Integer,ArrayList<Integer>> clusterSet){

    HashMap<Integer,LinkedList<Node> > dist_matrix= new HashMap<Integer ,LinkedList<Node>>(2173370);
    try{
    File myFile = new File(fileName);
    FileReader reader = new FileReader(myFile);
    BufferedReader breader = new BufferedReader(reader);

    System.out.println("start to load the distance matrix to memory . ");
    String line = null;
    int index = 0;
    LinkedList<Node> top50 = null;
    int userA = -1;
    while((line = breader.readLine())!=null){
    if(index%1000000==0){
    System.out.println("have loading "+index+" lines .");
    }
    if(index%50 == 0){
    top50 = new LinkedList<Node>();
    String [] tokens = line.split(",");
    ArrayList<Integer> set = new ArrayList<Integer>();
    userA = Integer.parseInt(tokens[0]);
    set.add(userA);
    clusterSet.put(userA, set);
    }

    String [] tokens = line.split(",");
    int userB = Integer.parseInt(tokens[1]);
    int distance = Integer.parseInt(tokens[2]);
    Node node = new Node(userB,distance);
    //HashMap<Integer, LinkedList<Node>> top50;
    top50.addLast(node);
    if(index%50 == 49){
    //System.out.println("the length of top50 is : "+top50.size());
    dist_matrix.put(userA, top50);
    }
    index++;
    }

    breader.close();
    System.out.println("finish getting the distance matrix . ");
    }catch(Exception x ){x.printStackTrace();}

    return dist_matrix;
    }



    class Node implements Comparable<Node>{

    Integer imsi;
    Integer dist;

    public int compareTo(Node s){
    return dist.compareTo(s.getDist());
    }

    public Node(){
    imsi = -1;
    dist = Integer.MAX_VALUE;
    }

    public Node(Integer imsiNo,int distance){
    imsi = imsiNo;
    dist = distance;
    }

    public int getDist(){
    return dist;
    }

    public Integer getImsi(){
    return imsi;
    }

    public void setImsi(Integer _imsi){
    imsi = _imsi;
    }
    }
    21 条回复    2014-09-01 10:34:41 +08:00
    plucury
        1
    plucury  
       2014-08-31 22:22:32 +08:00   ❤️ 1
    怀疑是内存问题可以用jmap打印内存占用情况。但是既然cpu占用100%的话,可以考虑用jstack打印线程运行命令来看看,如果看起来没问题考虑启动时打印GC日志,看看是否有频繁的full gc
    cloud107202
        2
    cloud107202  
       2014-08-31 22:39:56 +08:00   ❤️ 1
    内存问题不是很了解,说点我想到的
    1. 插入过程有没有并发调用HashMap.get(),这个可能会发生死锁http://coolshell.cn/articles/9606.html
    2. 如果可能的话,考虑下redis这类内存数据库
    undeflife
        3
    undeflife  
       2014-08-31 23:15:17 +08:00   ❤️ 1
    如果是内存问题的话 应该会报OOM 或OOM:PermGen space的

    查查文件在2300万行左右是否有什么问题导致你在读取文件的时候卡住
    读取文件的代码会不会有什么bug引起问题
    怀疑LinkedList 的话完全可以把LinkedList换成Node[50]
    ahcat
        4
    ahcat  
       2014-08-31 23:25:15 +08:00 via iPhone   ❤️ 1
    换一个64位的jvm?
    buptlee
        5
    buptlee  
    OP
       2014-08-31 23:36:54 +08:00
    @ahcat 就是64bit啊。嘿嘿。
    lehui99
        6
    lehui99  
       2014-09-01 00:10:58 +08:00 via Android   ❤️ 1
    HashMap的内部实现是数组+链表,在key的数量扩大时有可能导致数组和所有链表的内存重新分配,并将其中所有的引用重新赋值到新的数组中并重新生成所有的链表,十分耗时。可以看一下HashMap的具体实现的详细分析: http://blog.csdn.net/blog/vking_wang/14166593 ,中的最后一段:rehash过程。
    解决方法是在HashMap初始化时指定initCap,如: new HashMap(130000000/50) 。
    akfish
        7
    akfish  
       2014-09-01 06:09:39 +08:00 via iPad   ❤️ 1
    数据固定么?需要改动么?改了需要写回文件么?
    既然数据格式已知,完全没必要用HashMap和LinkedList,直接分配一个巨大的Array,减少overhead。对应key的index很容易计算,或者用一个HashMap<Integer, Integer>来记录每个key的第一个index。
    另外就是一次完成分配可以提高分配到连续内存的概率,减少碎片的产生。
    nybux
        8
    nybux  
       2014-09-01 08:43:50 +08:00   ❤️ 1
    HashMap在构造的时候加个参数
    new HashMap<>(2400001);
    buptlee
        9
    buptlee  
    OP
       2014-09-01 09:20:19 +08:00
    @lehui99 好的,试试看。谢谢兄弟。
    stevenyou
        10
    stevenyou  
       2014-09-01 09:27:39 +08:00   ❤️ 2
    楼主对内存的估算不太正确, 没有算进去java object , 和HashMap 的overhead.
    一个空object 会用16 bytes, Node 里有两个int, 也就是一个Node 会在Heap里用掉32 bytes
    一个空LinkedList里应该有4个private fields 会占用 (4+2) * 8 = 48 bytes
    一个LinkedList用的memroy 是 48 + 24* size_of_list , 你的size 是50 , 也就是 1248 bytes

    HashMap 的内存占用是 32 * SIZE + 4 * CAPACITY bytes , 你的size 是 120,000,000 / 50 . default load factor 是0.75, capacity 就是 (120,000,000 / 50) / 0.75.

    估算一下, 一共会用7个多G 的内存。

    另外hashmap rehash 是很快的,这个数据量不是很大, cpu没有吃满应该是别的原因,比如disk io, 看一下你top 里的 wa 是多少。

    楼主也可以把代码贴出来给大家看看,分析一下。
    buptlee
        11
    buptlee  
    OP
       2014-09-01 09:31:35 +08:00
    @cloud107202 没有并发问题,有没有redis的入门资料呢,贴个链接啥的,不太懂这个,嘿嘿。
    stevenyou
        12
    stevenyou  
       2014-09-01 09:32:21 +08:00   ❤️ 1
    cpu使用率后来达到了100% 应该是在rehash
    buptlee
        13
    buptlee  
    OP
       2014-09-01 09:38:19 +08:00
    @stevenyou 贴出来啦,您给看看。嘿嘿。
    stevenyou
        14
    stevenyou  
       2014-09-01 09:52:25 +08:00   ❤️ 2
    @buptlee 应该没什么问题, 像 @lehui99 说的在HashMap初始化时指定initCap 应该可以解决这个问题。
    建议是,如果你对内存使用在意的话,可以使用别的一些数据类型(Gnu Trove 里的TIntObjectHashMap, TIntArrayList etc. )在几乎不损失性能的同时把总内存降低到2G ~ 3G
    stevenyou
        15
    stevenyou  
       2014-09-01 09:53:13 +08:00   ❤️ 1
    buptlee
        16
    buptlee  
    OP
       2014-09-01 10:06:02 +08:00
    @stevenyou 我试了在声明hashmap时初始化个数的方法:
    HashMap<Integer,LinkedList<Node> > dist_matrix= new HashMap<Integer ,LinkedList<Node>>(2173370);
    结果还是停在了2300万行的位置然后cpu到100%了,查看进程管理器,发现内存只占用了2.2G左右,但我给这个程序分配了最大6G的内存,分配的方法是:
    在Debug Configuration里的(x)=Aguments的Program arguments下添加如下参数:
    -Xms1024m
    -Xmx6144m
    -XX:PermSize=1024m
    -XX:MaxPermSize=6144m
    您看看是不是我分配方法有问题,我的疑问是,就算是内存不足,也不应该卡在2.2G这个地方,而是应该在6G,或者至少应该4,5G啊。thanks.
    stevenyou
        17
    stevenyou  
       2014-09-01 10:10:34 +08:00   ❤️ 2
    跑的时候在java opts里加个 -Xrunhprof:cpu=samples,interval=10,depth=8
    程序停掉的时候目录下会多一个"java.hprof.txt", 看那里面profiler 显示哪个function 用的时间多。 这个是发现问题最容易和方法。 hprof是java 自带的profiler
    stevenyou
        18
    stevenyou  
       2014-09-01 10:20:21 +08:00   ❤️ 1
    还有,你用的内存都是在heap里,所以不需要设 PermSize , default size 32M or 96M based on 32bit or 64 bit is big enough
    buptlee
        19
    buptlee  
    OP
       2014-09-01 10:25:58 +08:00
    是应该在program arguments下设置还是VM arguments下设置呢?
    下面的参数可以吗?
    -Xms1024m
    -Xmx6144m
    stevenyou
        20
    stevenyou  
       2014-09-01 10:33:20 +08:00   ❤️ 1
    VM, 传给java的
    如果你放在program arguments里, 就可以解释为什么会卡在2.3G了, jvm deault heap size 是2048m, jvm在 GC , 在3 次full GC,释放不了2%内存的话就会throw out of memory exception: excessive GC time
    buptlee
        21
    buptlee  
    OP
       2014-09-01 10:34:41 +08:00
    @stevenyou cool! I will have a try as you say! Thanks bro.=.=
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   5320 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 40ms · UTC 09:09 · PVG 17:09 · LAX 02:09 · JFK 05:09
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.