以前用c ,现在用java我发现两种语言用法上区别不太大,但是在编程思路上却又区别,c 什么都要自己做,但是如果做的很严谨是不会出现内存泄露的问题,但是c 太灵活以至于可用性确实降低了,什么都需要自己考虑,而java在内存回收上有垃圾回收机制,在可用性上比c 要好一点,但是java的内存泄露却更加的隐蔽,今天我来谈谈java与c 内存泄露的区别:
1.c 的内存泄露的概念很简单,就是你手动申请内存了,但是却没有释放,导致内存泄露。但是这种问题很是明显,如果细心查找应该能查找出来
2.java的内存泄露:很多书上对java的内存泄露是这么解释的,内存泄露就是你以后都不会再使用的实例,没有被垃圾回收这样就会发生内存泄露,这个问题其实有点模棱两可,没有明确的界限,首先我将一下什么时候会进行垃圾回收:
书上说,当一个实例,没有任何引用,指向它的时候,那么这个实例在垃圾回收到来的时候就会被回收,那么怎么判断一个实例是否可以被回收呢?当前主要有两种判断的算法,第一种是引用计数器法,就是说给每一个对象都设置一个引用计数器,来存储当前有多少个引用指向它,就是新增加一个引用,引用计数器就加1,减少一个引用引用计数器减1,但是这种算法最大的缺点就是--循环相互引用的问题,所以现在一般虚拟机当中不会用这种算法,那么第二种算法就是找出一些实例作为“GC Roots”,也就是垃圾回收的根节点,如果说从GC Roots节点找不到一条到实例的链路的时候,那么这个节点就可以被回收了,GC Roots 一般包含,静态变量引用的对象,虚拟机栈中引用的对象,本地方法栈中引用的对象和方法区中的常量对象(就是有final修饰的对象)。
这时候java内存泄露的含义就渐渐,清晰了第一什么是不用的对象,那么不用的对象分为两种:
第一种是你主观认为不调用的对象,例如在一个方法中创建一个对象,你只是用它完成一个功能,但是完成功能后就不再使用这个对象,如果说你这个方法运行的时间很短,那么当你方法调用完方法的时候这个对象自然变成了可回收的对象。有很多人问为什么一个方法,运行完毕,那么它内部的局部实例就都会被回收呢,原理是这样的一个线程对应着一个jvm栈,而线程中的方法对对应着jvm栈中的一个栈帧,当调用这个方法的时候,栈帧就会入栈,方法运行完毕后栈帧就会出栈,而栈帧包含有局部变量表,操作数栈,返回地址以及动态链接,那么方法的参数,与方法中的局部变量就存放在局部变量表的当中(实例的引用由一个slot槽存放),上面不是说到,GC Roots包含有虚拟机栈中引用的对象吗,当一个方法调用完毕,就会对应着栈帧的出栈,那么这时候实例的引用就不在虚拟机栈当中了,也就是从GC Roots找不到对象了,所以对象就自然会被回收。这是你方法运行的时间较短的时候,这样很明显不存在内存泄露的问题,但是当你的方法运行的时间很长的话,那么你的实例就不会得到回收,这就出现了内存泄露的问题,所以你用完对象后就必须把你的引用设置成null。这就说明不适用的对象不一定会造成内存泄露也分情况而定。
第二种是客观不能再被调用的对象,例如程序运行超出了,对象的作用域,那么这个对象就不可能被调用到,还是那个问题,就是你方法时间短的话就不会出现内存泄露的问题,但是当你方法运行时间长的时候,就可能会出现内存泄露,为什么会说可能会出现内存泄露呢,是因为jvm的某种机制,就可能不会出现内存泄露,上面不是说,局部变量都是存放在局部变量表中吗,局部变量就存储在slot槽当中的,当你对象超出了作用域之后,slot槽中的引用并不会消失,这是造成内存泄露的主要原因,但是jvm为了节省slot槽的使用数量,会复用slot槽,也就是说当你的对象超出作用域之后,那么对应的slot槽就会变成可以覆盖的空间了,这是你如果再定义一个局部变量的话,那么这个槽就会被覆盖,那么对应实例的引用也被覆盖了,实例就可以被回收了,所以就不会发生内存泄露,但是如果之后不再定义新的变量的话,那么引用也就不会被覆盖,还是会发生内存泄露的问题,所以还是建议,当一个对象被用完的时候就把它的应用设置成null,这样就不会发生内存泄露了。
很明显,java中的内存泄露比c 中的内存泄露复杂的多,而且要隐蔽的多,所以现在想起那句话,我才理解,为什么说垃圾回收是一堵高墙,搞java的人想出去,搞c 的人想进去,我认为这就是两种语言有利有弊,c 太灵活,易用性比较差,但是所展现的问题比较清晰,而java比较规整,并且是真正的oo语言,所以易用性更加好一点,但是它存在的问题也就比较复杂,比较隐蔽的,如果不深究这些问题是很难发现的。