java中100万个引用集合中的内存分配

时间:2016-05-14 14:22:37

标签: java memory-management arraylist reference linked-list

我创建了一个包含100万个MyItem对象的ArrayList,消耗的内存为106mb(从任务管理器中检查)但是通过addAll()方法将相同的列表添加到另外两个列表后,需要259mb。我的问题是我只添加了对list的引用,在100万之后没有创建新对象。为什么在使用LinkedList时内存消耗增加了(因为它不需要连续的内存块所以不会进行重新分配)?

如何有效地实现这一目标?数据通过我的程序中的各种列表并消耗超过1GB的内存。上面介绍了类似的情况。

public class MyItem{
private String s;
private int id;
private String search;

public MyItem(String s, int id) {
    this.s = s;
    this.id = id;
}

public String getS() {
    return s;
}

public int getId() {
    return id;
}


public String getSearchParameter() {
    return search;
}

public void setSearchParameter(String s) {
    search = s;
}
}

public class Main{
    public static void main(String args[]) {
        List<MyItem> l = new ArrayList<>();
        List<MyItem> list = new LinkedList<>();
        List<MyItem> list1 = new LinkedList<>();

        for (int i = 0; i < 1000000 ; i++) {
            MyItem m = new MyItem("hello "+i ,i+1);
            m.setSearchParameter(m.getS());
            l.add(i,m);
        }

        list.addAll(l);

        list1.addAll(l);
        list1.addAll(list);

        Scanner s = new Scanner(System.in);
        s.next();//just not to terminate 
    }
}

2 个答案:

答案 0 :(得分:4)

无论何时你在场景后面调用LinkedList.addAll,它都会为每个添加的元素创建一个LinkedList.Node,所以在这里你创建了 3百万个这样的节点,它们不是免费的,实际上:

  1. 此对象有3个引用,知道引用的大小为4 bytes上的32-bit JVM64-bit JVM的{​​{1}}(-XX:+ UseCompressedOops),这是默认情况下,Java 7及更高版本中的堆小于UseCompressedOops32 GB上的8 bytes禁用64-bit JVM(-XX:-UseCompressedOops)。因此,根据您的配置,它提供 12字节 24字节
  2. 然后我们在UseCompressedOops8 bytes32-bit JVM添加16 bytes标题字段的大小。因此,根据您的配置,它提供 8字节 16字节
  3. 因此,如果我们总结一下:

      {li> 20字节 64-bit JVM 上的每个实例 启用了32-bit JVM的{​​{1}}上的每个实例
    1. 28字节
    2. 40字节 64-bit JVM上每个实例已禁用UseCompressedOops
    3. 当您在64-bit JVM上调用3次UseCompressedOops个100万个对象时,它会给出

      1. 60 Mo addAll
      2. 启用LinkedList的{​​{1}}上的
      3. 84 Mo
      4. 120 Mo 32-bit JVM 64-bit JVM已停用
      5. 其余的可能是垃圾收集器尚未收集的对象,您应该在加载UseCompressedOops后尝试调用64-bit JVM以获取实际大小并在加载{{UseCompressedOops后执行相同操作1}}。

        如果您想获得给定对象的大小,可以使用SizeOf

        如果您使用System.gc()并且想知道是否启用了ArrayList,只需在仅包含LinkedList个选项的终端中启动您的java命令,然后添加64-bit JVM例如,如果我的命令是UseCompressedOops,则启动-X,输出的开头应如下所示:

        -XX:+PrintFlagsFinal | grep UseCompressedOops

        在这种情况下,标志java -Xms4g -Xmx4g -XX:MaxPermSize=4g -cp <something> <my-class>已启用

答案 1 :(得分:3)

LinkedListdoubly-linked list,因此列表中的元素由节点表示,每个节点包含3个引用。

来自Java 8:

private static class Node<E> {
    E item;
    Node<E> next;
    Node<E> prev;

    Node(Node<E> prev, E element, Node<E> next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}

由于您使用了大量内存,因此可能没有使用压缩OOP,因此引用可能是64位,即每个8字节。

对象头为16字节+每个引用8字节,一个节点占用40个字节。拥有100万个元素,即40 Mb。

两个列表是80 Mb,然后记住Java内存被分段为池,对象(节点)被移动,你的内存消耗额外的153 Mb现在似乎是正确的。

注意:Arraylist每个元素只使用8个字节,而不是40个字节,并且如果你预先分配了自从你知道大小就可以做的后备数组,那么就可以节省大量的内存