对于一项作业,我们被要求使用单个链表实现数据结构。
我的问题是,在添加项目然后删除它们之后,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:这些是内存限制
谢谢。
答案 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用作字段名称和通用类型名称。