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

java中100万个引用集合中的内存分配,java,memory-management,arraylist,reference,linked-list,Java,Memory Management,Arraylist,Reference,Linked List,我创建了一个包含100万个MyItem对象的ArrayList,消耗的内存为106mb(从任务管理器中检查),但通过addAll()方法将同一个列表添加到另外两个列表后,需要259mb。我的问题是我只添加了对列表的引用,在这100万之后没有创建新对象。为什么即使使用了LinkedList,内存消耗仍会增加(因为它不需要连续的内存块,所以不会进行重新分配) 如何有效地实现这一点?数据在我的程序中通过各种列表,并消耗超过1GB的内存 public class MyItem{ private Stri

我创建了一个包含100万个MyItem对象的ArrayList,消耗的内存为106mb(从任务管理器中检查),但通过addAll()方法将同一个列表添加到另外两个列表后,需要259mb。我的问题是我只添加了对列表的引用,在这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 
    }
}
公共类MyItem{
私有字符串;
私有int-id;
私有字符串搜索;
公共MyItem(字符串s,整数id){
这个.s=s;
this.id=id;
}
公共字符串get(){
返回s;
}
公共int getId(){
返回id;
}
公共字符串getSearchParameter(){
返回搜索;
}
public void setSearchParameter(字符串s){
搜索=s;
}
}
公共班机{
公共静态void main(字符串参数[]){
列表l=新的ArrayList();
列表=新建LinkedList();
List list1=新链接列表();
对于(int i=0;i<1000000;i++){
MyItem m=新的MyItem(“你好”+i,i+1);
m、 setSearchParameter(m.getS());
l、 加(i,m);
}
列表。添加全部(l);
清单1.addAll(l);
列表1.addAll(列表);
扫描仪s=新的扫描仪(System.in);
s、 next();//只是不终止
}
}
任何时候你在幕后调用它都会为每个添加的元素创建一个节点,因此你在这里创建了300万个这样的节点,这不是免费的,事实上:

  • 这个对象有3个引用,知道在
    32位JVM
    64位JVM
    上引用的大小是
    4个字节
    ,使用
    UseCompressedOops
    (-XX:+UseCompressedOops)启用,默认情况下,在Java 7和更高版本中,堆小于
    32 GB,在
    64位JVM上
    8字节
    ,禁用
    UseCompressedOops
    (-XX:-UseCompressedOops)。因此,根据您的配置,这里给出了12字节24字节
  • 然后我们添加头字段的大小,在
    32位JVM
    上为
    8字节,在
    64位JVM上为
    16字节。因此,根据您的配置,这里给出了8个字节16个字节
  • 因此,如果我们总结一下需要:

  • 32位JVM上的每个实例20字节
  • 64位JVM
    上启用了
    UseCompressedOops
    时,每个实例的28字节
  • 64位JVM上的每个实例40个字节,禁用了
    UseCompressedOops
  • 当您在
    LinkedList
    上对100万个对象进行3次
    addAll
    调用时,它会给出

  • 60个月
    32位JVM上
  • 84 Mo
    64位JVM
    上启用
    UseCompressedOops
  • 120 Mo在禁用
    64位JVM的情况下
  • 其余的可能是垃圾收集器尚未收集的对象,您应该在加载
    ArrayList
    后尝试调用
    System.gc()
    ,以获取实际大小,并在加载
    LinkedList
    后执行相同的操作

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

    如果您使用的是
    64位JVM
    ,并且想知道是否启用了
    UseCompressedOops
    ,只需在一个只有
    -X
    选项的终端中启动java命令,并添加
    -XX:
    ,例如,如果我的命令是
    java-Xms4g-Xmx4g-XX:MaxPermSize=4g-cp
    ,启动java-Xms4g-Xmx4g-XX:MaxPermSize=4g-XX:+PrintFlagsFinal | grep UseCompressedOops
    ,输出的开头应该如下所示:

         bool UseCompressedOops                        := true            {lp64_product}
         ...
    

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

    LinkedList
    是a,因此列表中的元素由节点表示,每个节点包含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;
        }
    }
    
    私有静态类节点{
    E项目;
    节点下一步;
    节点前置;
    节点(上一个节点、E元素、下一个节点){
    this.item=元素;
    this.next=next;
    this.prev=prev;
    }
    }
    
    由于使用了大量内存,可能没有使用压缩OOP,因此引用可能是64位的,即每个引用8字节

    对于每个引用16字节+8字节的对象头,节点占用40字节。如果有100万个元素,这将是40 Mb

    两个列表是80MB,然后请记住Java内存被分割成池,对象(节点)被移动,另外153MB的内存消耗现在看起来差不多了


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

    我想LinkedList.Node将被分配引用!如果没有,我如何在不分配更多节点的情况下工作;(感谢您的回复!它就是这样做的,但它会为每个添加的元素创建一个LinkedList.Node实例,以保留上一个和下一个节点ArrayList根据其名称在数组中表示。如果数组太小,则会创建两倍的大小,并将引用复制到新数组中。因为Java有自己的内存管理第一个数组的内存将仅释放给JVM内部。由于您使用了大量内存,可能没有使用压缩OOP,默认情况下,如果堆小于32 GB,64位JVM上会启用UseCompressedOops,如果不是这样,我会感到惊讶,因为这只是一个简单的问题