系统相关
首页 > 系统相关> > java – 清空后单个链表仍然占用内存

java – 清空后单个链表仍然占用内存

作者:互联网

参见英文答案 > How to free memory in Java?                                    13个
>            Does GC release back memory to OS?                                    4个
>            Does the JVM give back free memory to the OS when no longer needed?                                    3个
>            java free memory usages                                    1个
>            Limit jvm process memory on ubuntu                                    2个
对于作业,我们被要求使用单个链表实现数据结构.

我的问题是,在添加项目然后删除它们之后,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
和count = 0;

为什么会这样? deck是空的,没有任何项目,但内存仍然像以前一样使用.

代码通过了所有正确性测试但在内存上失败了.

我还看了一步一步的调试器,并正在做应该做的事情.

可能是在removeFirst()中我没有设置任何null为空而只是首先分配为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

谢谢.

解决方法:

从我的测试看来,内存仍然被分配,但等待垃圾收集. 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)
    {
    }
}

将那些代替main()放在你的Deque类中并运行它.我明白了:

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. (忽略最大数字.这没有报告我的想法.)它在分配后使用了19个,其中19个是不计其数.

在释放Deque中的所有节点之后,立即没有任何变化!

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

正如我在评论中所说,添加到堆中的额外14MB左右没有返回到操作系统,我并不感到惊讶.如果在Windows中使用任务管理器,则该内存仍将被视为“正在使用”.转向操作系统获取内存是很昂贵的,所以通常情况下这只是为了扩展大块(大约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< Item>.这是应该的,因为节点项类型是通用的.在同一份报告中,这将内存使用量从19MB减少到15MB.非静态内部类将内部类的每个实例绑定到周围类的特定实例.你不需要它,它需要时间和内存.

该修复可能有助于您通过测试.

哦,是的,请注意字段名称从下一个更改为下一个,项目更改为项目.如果要符合传统的Java命名样式,请使用小写字母启动字段和方法名称.在任何情况下,您都不应该为字段名称和泛型类型名称使用Item.

标签:java,memory,data-structures,singly-linked-list
来源: https://codeday.me/bug/20190710/1425328.html