为啥被誉为经典书的effective java第六条的所谓内存泄露代码能长跑不出异常?

响马0709 2016-07-27 12:54:45
如题, effective java, 第二版,第六条,作者说以下代码有问题,会导致内存泄露:
package Item6;
import java.util.*;
class EmptyStackException extends IllegalStateException {

}
public class Stack {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;

public Stack() {
elements = new Object[DEFAULT_INITIAL_CAPACITY];
}

public void push(Object e) {
ensureCapacity();
elements[size++] = e;
}

public Object pop() {
if (size == 0)
throw new EmptyStackException();
return elements[--size];
}

/**
* Ensure space for at least one more element, roughly
* doubling the capacity each time the array needs to grow.
*/
private void ensureCapacity() {
if (elements.length == size)
elements = Arrays.copyOf(elements, 2 * size + 1);
}

// 我特意加了main函数跑跑试试,结果长时间后根本不爆所谓outofmemoryerror错误,
public static void main(String[] args) {

Stack stack = new Stack();
while(true) {
for (long i = (long) 0; i < 1000000; i++) {
Object object = new Object();
stack.push(object);
}

System.out.println("pop from stack----------------");
for (long i = 0; i < 1000000; i++) {
stack.pop();
}
}

}
}


请教各位高手,理论上那个pop()函数似乎确实有问题,没把弹出元素引用置空,但怎么实际长时间一运行,根本不见异常啊???
...全文
520 14 打赏 收藏 转发到动态 举报
写回复
用AI写文章
14 条回复
切换为时间正序
请发表友善的回复…
发表回复
响马0709 2016-08-18
  • 打赏
  • 举报
回复
40分全给你了!
引用 11 楼 qq_35209952 的回复:
其实你写 while(true){ stack.push(); stack.pop(); syso(stack.getSize()) } 就好了. ... 作者应该是认为,讲到这种程度读者肯定明白了,就不废话了
引用 11 楼 qq_35209952 的回复:
其实你写 while(true){ stack.push(); stack.pop(); syso(st 作者应该是认为,讲到这种程度读者肯定明白了,就不废话了
ack.getSize()) } 就好了. ...
JPF1024 2016-07-28
  • 打赏
  • 举报
回复
可能已经被修复了,如果你要试类似的问题,一定要用跟书上说的JDK版本一样。
gloomyfish 2016-07-28
  • 打赏
  • 举报
回复
千万不要相信那些书上讲的东西,JDK每个版本都在断修复BUG 他们总结也是基于经验,都是从来没见过JVM的代码,所谓的经验并一定适用现在JDK8 而且JDK8与之前的在内存分配上与之前的有很大不同,这个可以参考甲骨文官方文档。 所以在JDK、JVM参数调优方面,根本就是没有圣经!也不应该相信圣经!
逗泥丸的平方 2016-07-28
  • 打赏
  • 举报
回复
其实你写
while(true){
stack.push();
stack.pop();
syso(stack.getSize())
}
就好了. ...

作者应该是认为,讲到这种程度读者肯定明白了,就不废话了
逗泥丸的平方 2016-07-27
  • 打赏
  • 举报
回复
引用 3 楼 xiang_ma 的回复:
上述代码跑了半个钟,不仅程序完全正常还在跑,从win10的任务管理器的内存监控来看,跑起来后,还照样有500M--1g的内存可用,有起伏,但可控,。。。
32位系统,一个空的引用应该是4byte,64位是8byte 另外,最大的问题是, 你的object 在后面没有被引用,应该是可以被回收的对象.... 再说一下关于监控,Windows看到的内存基本上是不靠谱的.. 如果想了解java虚拟机的消耗建议你这样做: 运行时加睡眠时间(或者直接进行debug中断调试) 可以加均匀的给jconsole观察一下内存使用变化,(可以大概的看到内存是在什么时候被回收的) 然后也可以在最后加一个长睡眠,用jmap来分析一下究竟有多少对象.
痴傻二呆萌 2016-07-27
  • 打赏
  • 举报
回复
可能是JDK版本的问题吧. 后续修复了也说不定.
响马0709 2016-07-27
  • 打赏
  • 举报
回复
上述代码跑了半个钟,不仅程序完全正常还在跑,从win10的任务管理器的内存监控来看,跑起来后,还照样有500M--1g的内存可用,有起伏,但可控,。。。
响马0709 2016-07-27
  • 打赏
  • 举报
回复
引用 1 楼 qq_35209952 的回复:
1000000 也就1M吧. object 也就4~8b吧? 这也就占用10兆的样子... 把jvm改小一点,加一点延迟跟踪一下内存试试?或者用一些更大的对象(javabean呀,或者其他什么的)
楼上,我把推入栈中的类改变成大对象,定义为 class BigObj { private long arry[] = new long[1000000]; }//一个对象起码8M字节, 够大吧? 然后main函数循环改成 int loop = 160;// 入栈,出站的循环次数 Stack stack = new Stack(); while(true) { for (long i = (long) 0; i < loop; i++) {// 入栈循环次数 不能过大,否则堆内存不够用 BigObj object = new BigObj(); stack.push(object); } System.out.println("pop from stack----------------"); for (long i = 0; i < loop; i++) { stack.pop(); } } 长时间循环运行上述代码,根本不爆所谓内存泄露错误,从理论分析上来说,入栈的那个new BigObj()在被栈内的数组引用后,第二次再有元素入栈时,同一位置引用指向新的new BigObj(), 失去引用的对象应该被jvm发现而垃圾回收啊! 所以,从理论上也是不内存泄露的,对吗? 理论和实际运行结果完全对的起来啊!
逗泥丸的平方 2016-07-27
  • 打赏
  • 举报
回复
1000000 也就1M吧. object 也就4~8b吧? 这也就占用10兆的样子... 把jvm改小一点,加一点延迟跟踪一下内存试试?或者用一些更大的对象(javabean呀,或者其他什么的)
响马0709 2016-07-27
  • 打赏
  • 举报
回复
引用 8 楼 qq_35209952 的回复:
[quote=引用 5 楼 qq_35209952 的回复:] 另外,最大的问题是, 你的object 在后面没有被引用,应该是可以被回收的对象.... .
其实更问题在于 你所谓的pop其实是啥也没干... 你并没有根据栈的特性来操作栈,你只是当做一个数组把它初始化,然后做了指针偏移 你的栈里面一直是那么大 所以也体会不出来内存泄漏的感觉.[/quote] 嗯,谢谢楼上热心的回应,我同意你的说法,那个栈实现的pop代码之所以有内存泄露问题,正是因为疏忽了栈的pop()的本意,本意应该是:既然弹出元素,那被弹出的元素就应该马上让系统回收,而不仅仅只是减小一下栈顶指示index, 设想如果在往这个栈中推入大量对象,导致系统内存逼近可用极限时,(但还没到极限),紧接着如果作者按原pop()这样弹出全部元素但不释放内存,然后作者以为因为“我已经把栈内元素全部弹出了啊!这样被占堆内存应该大幅度下降了,所以,我就可以放心的再做别的使用大量堆内存的其他代码了,(与这个栈的无关,但需要大量堆内存的代码),他很可能马上发现,其实,pop全部栈元素根本没有起到释放内存的效果,堆内存仍然极为紧张,再分配大量堆内存马上报内存溢出,这就是因为pop()内少了一句:elements[size] = null; 而这段有问题的代码之所以在无限循环重复做:先推入栈,后再弹出,再推入。。。。的测试中没有报错,是因为:在第二次重复推入栈的时候,同一位置引用指向新的new BigObj(), 失去引用的第一次入栈的对象被jvm发现而垃圾回收啊!因祸得福啊! 但是这个是jvm的功劳,如果是c/c++实现,重复推入,弹出,再推入,。。。就没有这么好的运气了,因为c/c++没这个发现失去引用的对象自动回收的功能。 总之,原来的栈实现pop函数的确有内存泄露问题,只是碰巧在做无限循环重复做:先推入栈,后再弹出,再推入的测试中,因为jvm的功能而没被发现罢了! 不过,要求effecitve java的作者再这样举出一个无限循环反复推入,弹出,再推入的例子,然后说明这段代码尽管有问题,但却没有异常以及原因,似乎对作者的要求高了些!是不是? 作者太尽心了,哈哈! 以上,是我的理解,不知哪位高人有更好,更高的斧正? 我的main()测试函数: public static void main(String[] args) { int loop = 160; Stack stack = new Stack(); while(true) { for (long i = (long) 0; i < loop; i++) { BigObj object = new BigObj(); stack.push(object); } System.out.println("pop from stack----------------"); for (long i = 0; i < loop; i++) { stack.pop(); } } }
逗泥丸的平方 2016-07-27
  • 打赏
  • 举报
回复
或者你贴一下main里完整的代码吧.. 不是很了解你现在各个变量的作用域
逗泥丸的平方 2016-07-27
  • 打赏
  • 举报
回复
引用 5 楼 qq_35209952 的回复:
另外,最大的问题是, 你的object 在后面没有被引用,应该是可以被回收的对象.... .
其实更问题在于 你所谓的pop其实是啥也没干... 你并没有根据栈的特性来操作栈,你只是当做一个数组把它初始化,然后做了指针偏移 你的栈里面一直是那么大 所以也体会不出来内存泄漏的感觉.
响马0709 2016-07-27
  • 打赏
  • 举报
回复
引用 6 楼 bree06 的回复:
把你的测试代码换成下面代码就会爆了,
public static void main(String[] args) {
    Stack stack = new Stack();
    for (int i = 0; i < Integer.MAX_VALUE; i++) {
        Object object = new Object();
        stack.push(object);
    }
}
引用 6 楼 bree06 的回复:
把你的测试代码换成下面代码就会爆了,
public static void main(String[] args) {
    Stack stack = new Stack();
    for (int i = 0; i < Integer.MAX_VALUE; i++) {
        Object object = new Object();
        stack.push(object);
    }
}
汗,楼上,你只往栈里推,不往外弹,栈所引用到的堆内存越来越多,哪个语言实现这段只进不出的代码不爆? 这叫内存溢出,不够用,不是内存泄露,所谓泄露,就是原来被分配后的内存,在失去利用价值后,应该还给系统以重复利用,但却没还给系统,导致系统可用内存越来越少,你的上述代码不在问题的关键点子上,我觉得
bree06 2016-07-27
  • 打赏
  • 举报
回复
把你的测试代码换成下面代码就会爆了,
public static void main(String[] args) {
    Stack stack = new Stack();
    for (int i = 0; i < Integer.MAX_VALUE; i++) {
        Object object = new Object();
        stack.push(object);
    }
}

62,615

社区成员

发帖
与我相关
我的任务
社区描述
Java 2 Standard Edition
社区管理员
  • Java SE
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧