所以我最近尝试使用Java中的单个链表实现Stack,我遇到了这个问题。如果我自然地将100000个元素推送到堆栈,则内存会增加,因为堆栈需要100000个节点来存储对象。但是,当堆栈清空时(在100,000次弹出之后),对这些节点及其内容的引用应该超出范围,这样如果堆栈填满,则内存使用量不应增加。但是,当我的代码中发生这种情况时,内存会翻倍。换句话说,pop似乎没有充分删除引用或允许垃圾收集,我希望有人可以告诉我为什么。
这是堆栈类
$f11
这是Node
的实现public class Stack<T> {
private Node<T> top;
private int size;
private long limit;
public boolean isEmpty() {
return this.size == 0;
}
public void setStackLimit(long limit) {
this.limit = limit;
}
public int size() {
return size;
}
public Stack() {
this(1000L);
}
public Stack(long limit) {
this.size = 0;
this.top = null;
this.limit = limit;
}
public void push(T o) throws StackOverflowException{
if (this.size == this.limit) throw new StackOverflowException("The stack overflowed");
if (this.top == null) this.top = new Node<T>(o);
else {
Node<T> r = new Node<T>(o);
Node<T> temp = this.top;
this.top = r;
this.top.setNext(temp);
}
this.size++;
}
@SafeVarargs
public final void mpush(T... o) throws StackOverflowException{
for (T ob: o) {
this.push(ob);
}
}
public T pop() throws EmptyStackException{
if (this.top == null) throw new EmptyStackException("The stack is empty");
else {
T o = this.top.getPayload();
this.top = this.top.getNext();
this.size--;
return o;
}
}
public boolean empty() {
while (!this.isEmpty()){
try {
this.pop();
}
catch (Exception e) {
return false;
}
}
return true;
}
public String printStack() {
String stack = "";
Node<T> temp = this.top;
while (temp != null){
if (temp.getPayload() instanceof Stack<?>) {
Stack<?> stack2 = (Stack<?>)temp.getPayload();;
stack = "| " + stack2.printStack() + stack;
} else
stack = "|" + temp.getPayload().toString() + stack;
temp = temp.getNext();
}
return stack.replaceAll("[| ]$","");
}
public T peek() throws EmptyStackException {
if (this.top == null) throw new EmptyStackException("The stack is empty");
else {
T o = this.top.getPayload();
return o;
}
}
public boolean isFull() {
return this.size == this.limit;
}
public Object[] toArray() {
Object[] returnvalue = new Object[this.size];
int index = this.size-1;
Node<T> temp = this.top;
while (index >= 0) {
returnvalue[index--] = temp.getPayload();
temp = temp.getNext();
}
return returnvalue;
}
public String toString() {
try {
return "<Stack object of size " + this.size() + " last element: " + this.peek().toString() + ">";
} catch (Exception e) {
return "<Empty stack object of size " + this.size() + ">";
}
}
}
答案 0 :(得分:0)
除非遇到真正的问题(例如OOM错误),否则你真的不应该担心这些事情。当你说了大约一千兆字节的内存时,我开始对此感兴趣,并决定试一试。我用了这段代码:
Stack<Integer> stack = new Stack<>(100_000_000L);
System.out.println(Runtime.getRuntime().maxMemory());
for (int j = 0; j < 100; ++j) {
System.out.println(j + "th run");
for (int i = 0; i < 10_000_000; ++i) {
stack.push(i);
}
System.out.println(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory());
Thread.sleep(10000);
stack.empty();
System.out.println(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory());
Thread.sleep(10000);
}
(顺便说一句,有long
限制但int
尺寸的重点是什么?)
第一行打印3795845120,我假设它对应于4 GB内存(有一些内存被分配给堆栈,类,JVM对象和其他非常有用的东西)。然后它进入循环并监视内存分析器(无论NetBeans 8.1使用什么)和Windows任务管理器。首次运行时,分析器报告了10 M个对象/ 400 MB内存,Windows报告了大约500 MB的内存使用量。到目前为止一切都很好。
在第4次运行时,它在分析器中类似于15 M对象/ 600 MB内存,但在任务管理器中大约为1 GB。不过,不是40 M对象!因此GC正在开展工作,虽然速度很慢。
在第8次运行时,分析器报告了25个M对象,内存增长到大约1700 MB。但是在第9次比赛中它回到了15 M物体并留在那里。任务管理器报告的内存停留在1700 MB,并且再也没有增加。
在其他地方,代码打印
0th run
406432424
408400384
1th run
408267736
409295992
2th run
602312032
603027096
3th run
620573368
621308040
4th run
614213120
615099616
5th run
616810512
617419624
6th run
615366040
616072952
7th run
620580088
621587032
8th run
1020848024
1022640736
9th run
627436904
628500048
您可以清楚地看到GC工作。
从这个实验中我猜测Java试图将内存消耗保持在给定的最大限制的50%以内。我的盒子有16 GB或RAM,所以我猜这就是默认限制为高的原因(默认情况下甚至使用服务器VM)。低于50%,GC仍在执行其工作,只有内存不会返回给操作系统。
为了测试这个假设,我使用-Xmx 1000M
运行相同的代码。结果有点令人惊讶:根据任务经理的说法,它保持在950M左右。这意味着算法并不像50%那么简单。它可能是智能的,并且针对硬件和环境进行了优化。
尽管如此,我们还是可以得出结论,GC正常运行(惊喜!)。顺便说一句,我真的认为你应该在发布问题之前完成我的几行代码,以展示一些研究工作。它不像是在任何地方努力或需要一些特殊技能。然后问题可能看起来更像“What is the memory management strategy of the JVM?”,谷歌搜索显示了很多有趣的链接。