单个链表清空后仍在消耗内存

时间:2018-07-02 16:58:12

标签: java memory data-structures singly-linked-list

对于一项作业,我们被要求使用单个链表实现数据结构。

我的问题是,在添加项目然后删除它们之后,java程序仍然使用与以前相同的内存。

这是一个简单的示例

Deque<Integer> deck = new Deque<>();
for( int i =0; i < 500000;i++) {
    deck.addFirst(i);
}

for( int i =0; i < 500000;i++) {
    deck.removeFirst();
}
System.out.println("end"); //Debugger here

添加50万个项目将消耗27mb的内存。 删除它们后仍在27mb。 最后在调试器中跳转,变量组具有first = null的字段 计数= 0;

为什么会这样?卡座是空的,没有任何物品,但是内存仍然像以前一样使用。

该代码通过了所有正确性测试,但在内存测试中却失败了。

我还看着逐步调试器,并且正在做应该做的事情。

可能是在removeFirst()中我没有将任何内容设置为null而是仅将first指定为first.next吗?

编辑:这是内存测试的输出

 Test 10: Total memory usage after inserting 4096 items, then successively
     deleting items, seeking values of n where memory usage is maximized
     as a function of n

               n        bytes
    ---------------------------------------------------
  => passed     3200        65592         
  => passed     1600        65592         
  => FAILED      800        65592   (1.7x)
  => FAILED      400        65592   (3.4x)
  => FAILED      200        65592   (6.7x)
  => FAILED      100        65592  (13.1x)
  => FAILED       50        65592  (25.3x)
  ==> 2/7 tests passed

您可以看到50个元素仍在使用65592

Edit2:

// remove and return the item from the front
public Item removeFirst() {
    if (this.isEmpty()) throw new java.util.NoSuchElementException();
    Item current = first.Item;
    first = first.Next;
    count--;
    return current;
}

这是removeFirst()

Edit3:

import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.concurrent.TimeUnit;

/**
 * Created by Alex on 6/30/2018.
 */

public class Deque<Item> implements Iterable<Item> {

    private Node first;

    private int count;

    private class Node {
        private Node Next;
        private Item Item;
    }

    // construct an empty deque
    public Deque() {
        first = null;
        count = 0;
    }

    // is the deque empty?
    public boolean isEmpty() {
        if (count == 0) {
            return true;
        }
        return false;
    }

    // return the number of items on the deque
    public int size() {
        return count;
    }

    // add the item to the front
    public void addFirst(Item item) {
        if (item == null) throw new IllegalArgumentException();
        Node temp = new Node();
        temp.Item = item;
        temp.Next = first;
        first = temp;
        count++;
    }

    // add the item to the end
    public void addLast(Item item) {
        if (item == null) throw new IllegalArgumentException();
        if (isEmpty()) {
            addFirst(item);
        } else {
            Node last = first;
            while (last.Next != null) {
                last = last.Next;
            }
            Node newLast = new Node();
            newLast.Item = item;
            newLast.Next = null;
            last.Next = newLast;
            count++;
        }
    }

    // remove and return the item from the front
    public Item removeFirst() {
        if (this.isEmpty()) throw new java.util.NoSuchElementException();
        Item current = first.Item;
        first = first.Next;
        count--;
        return current;
    }

    // remove and return the item from the end
    public Item removeLast() {
        if (isEmpty()) throw new java.util.NoSuchElementException();
        if (size() == 1) {
           return removeFirst();
        }else {
            Node newLast = first;
            Node oldLast = first;
            while (oldLast.Next != null) {
                newLast = oldLast;
                oldLast = oldLast.Next;
            }
            newLast.Next = null;
            count--;
          //  Item lastItem = ;
            return oldLast.Item;
        }
    }

    // return an iterator over items in order from front to end
    public Iterator<Item> iterator() {
        return new DequeIterator();
    }

    private void debug() {
        Iterator<Item> deckIter = iterator();
        while(deckIter.hasNext()) {
            System.out.println(deckIter.next());
        }
        System.out.println(isEmpty());
        System.out.println("Size:" + size());
    }

    // an iterator, doesn't implement remove() since it's optional
    private class DequeIterator implements Iterator<Item> {
        private Node current = first;
        public boolean hasNext()  { return current != null;                     }
        public void remove()      { throw new UnsupportedOperationException();  }

        public Item next() {
            if (!hasNext()) throw new NoSuchElementException();
            Item item = current.Item;
            current = current.Next;
            return item;
        }
    }

    // unit testing (optional)
    public static void main(String[] args) throws InterruptedException {
        Deque<Integer> deck = new Deque<>();

        for( int i =0; i < 500000;i++) {
            deck.addFirst(i);
        }

        for( int i =0; i < 500000;i++) {
            deck.removeFirst();
        }
        System.out.println("end");
        TimeUnit.MINUTES.sleep(5);
    }
}

Edit4:这些是内存限制

https://imgur.com/nQuDfUF

谢谢。

1 个答案:

答案 0 :(得分:0)

从我的测试看来,内存仍在分配,但是正在等待垃圾回收。 java.lang.Runtime类具有一些用于通知您内存使用情况的方法,以及一种用于建议运行垃圾回收的静态方法gc()。这是一个额外的方法和对main()类的修改,可能会对您有所帮助:

private final static long MB = 1024*1024;
static void memStats(String when)
{
    Runtime rt = Runtime.getRuntime();
    long max = rt.maxMemory();
    long tot = rt.totalMemory();
    long free = rt.freeMemory();
    System.out.printf(
        "mem(mb): %5d tot, %5d free %5d used %5d max %s\n",
         tot/MB, free/MB, (tot-free)/MB, max/MB, when);
}

// unit testing (optional)
public static void main(String[] args) {
    Deque<Integer> deck = new Deque<>();
    memStats("startup");
    for( int i =0; i < 500000;i++) {
        deck.addFirst(i);
    }
    memStats("after alloc");

    for( int i =0; i < 500000;i++) {
        deck.removeFirst();
    }
    memStats("after removal");
    Runtime.getRuntime().gc();
    memStats("after gc");

    System.out.println("end");
    try {
        TimeUnit.MINUTES.sleep(5);
    } catch (InterruptedException ex)
    {
    }
}

将它们放在Deque类中的main()位置并运行它。我得到:

mem(mb):    15 tot,    15 free     0 used   247 max startup
mem(mb):    29 tot,    10 free    19 used   247 max after alloc
mem(mb):    29 tot,    10 free    19 used   247 max after removal
mem(mb):    29 tot,    29 free     0 used   247 max after gc
end

如您所见,(1)分配给Java的内存从为Java对象分配的总内存开始为15MB,已使用0MB。 (忽略max数字。这没有报告我的想法。)分配后,使用19个磁盘后,它增加到29MB。

在释放Node中的所有Deque之后,没有任何变化!

但是,在gc()调用之后,请注意分配给堆的总内存没有更改,但是所有内存现在都可供其他Java对象使用。垃圾回收将最终发生-通常是在堆中没有足够的连续内存来满足new请求时。

正如我在评论中说的那样,我对添加到堆中的额外14MB左右未返回操作系统并不感到惊讶。如果您在Windows中使用任务管理器,则该内存仍将被视为“正在使用”。使用OS来存储内存是很昂贵的,因此通常只做大块扩展(在这种情况下,大约为1500万字节),并且只能在运行结束时才发布。但是,这是与实现有关的行为,并且在不同的JVM上可能有所不同。


关于代码的注释。我将Node类转换为:

private static class Node<Item> {
    Node<Item> next; // lowercase next
    Item item; // You were using Item for both a type and field name!?
}

...并将几乎每次出现的Node转换为Node<Item>。这是应该的,因为节点项类型是通用的。在同一份报告上,这将内存使用量从19MB减少到15MB。非静态内部类将内部类的每个实例绑定到周围类的特定实例。您不需要它,这会花费时间和内存。

该修补程序可以帮助您通过测试。

哦,是的,请注意字段名称从下一个更改为下一个,并将项目更改为项目。如果要适合传统的Java命名样式,请以小写字母开头的字段和方法名称。在任何情况下,都不应将Item用作字段名称和通用类型名称。