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

Jeg*_*abu 5 java memory-management reference linked-list arraylist

我创建了一个包含 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 
    }
}
Run Code Online (Sandbox Code Playgroud)

Nic*_*tto 5

任何时候你在LinkedList.addAll幕后调用,它都会LinkedList.Node为每个添加的元素创建一个,所以在这里你创建了300 万个这样的节点,这确实不是免费的:

  1. 该对象有 3 个引用,知道引用的大小已4 bytes打开32-bit JVM64-bit JVM启用UseCompressedOops(-XX:+UseCompressedOops),默认情况下,堆小于32 GBJava 7 及更高版本,并且8 bytes64-bit JVM禁用UseCompressedOops(-XX:-UseCompressedOops) )。所以这里根据你的配置它给出12 字节24 字节
  2. 然后我们添加标头字段的大小,即8 byteson32-bit JVM16 byteson 64-bit JVM。所以这里根据你的配置它给出8 字节16 字节

因此,如果我们总结一下,需要:

  1. 每个实例20 字节32-bit JVM
  2. 启用64-bit JVM后每个实例28 字节UseCompressedOops
  3. 64-bit JVM禁用UseCompressedOops时每个实例40 字节

当您addAll在 a 上调​​用 100 万个对象中的3 次时LinkedList,它会给出

  1. 6032-bit JVM
  2. 84 莫 Mo on64-bit JVM启用UseCompressedOops
  3. 120月64-bit JVMUseCompressedOops120残疾人

其余的可能是垃圾收集器尚未收集的对象,您应该尝试调用System.gc()在加载后调用ArrayList以获取实际大小,并在加载LinkedList.

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

如果您使用 a64-bit JVM并且想知道是否UseCompressedOops已启用,只需在终端中启动您的 java 命令,仅使用-X选项并添加-XX:+PrintFlagsFinal | grep UseCompressedOops,例如,如果我的命令是java -Xms4g -Xmx4g -XX:MaxPermSize=4g -cp <something> <my-class>,则启动java -Xms4g -Xmx4g -XX:MaxPermSize=4g -XX:+PrintFlagsFinal | grep UseCompressedOops,则输出的开头应如下所示:

     bool UseCompressedOops                        := true            {lp64_product}
     ...
Run Code Online (Sandbox Code Playgroud)

在这种情况下,该标志UseCompressedOops已启用


And*_*eas 4

LinkedList是一个双向链表,因此列表中的元素由节点表示,每个节点包含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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

如果对象头为 16 字节 + 每个引用 8 字节,则一个节点占用 40 字节。如果有 100 万个元素,则大小为 40 Mb。

两个列表是 80 Mb,然后记住 Java 内存被分割成池,并且对象(节点)会四处移动,现在额外消耗 153 Mb 的内存似乎是正确的。

注意:每个Arraylist元素仅使用 8 个字节,而不是 40 个字节,并且如果您预先分配后备数组(因为您知道大小,所以可以这样做),这样可以节省大量内存。