「Java基础第1篇」终结处理和垃圾回收器

1.背景:
java有垃圾回收器负责回收无用对象占据的内存资源,由于垃圾回收器只知道释放那些有new分配的内存,当对象使用非new方式获得了内存时,垃圾回收器就会失去作用。java允许在类中定义一个finalize()方法。

2.finalize()方法:
(1)大致流程:
当垃圾回收器准备好释放对象的存储空间时,将首先调用finalize()方法,在下一次垃圾回收动作发生时,才会真正的释放对象内存。
(2)垃圾回收与析构函数(C++中)的区分:
C++中对象一定会被销毁,销毁对象必须用到析构函数;
java中对象并非总是被垃圾回收;
(3)java并不能及时的销毁对象,要想及时的销毁对象,必须动手创建一个执行清理工作的普通方法。如果在finalize()里加入某种销毁功能,当垃圾回收(但却不一定能及时发生)时,finalize()得到调用,垃圾回收不发生时,自然得不到调用。默认情况下,只要程序没有达到存储空间用完的那一刻,对象的占用空间就总不会释放。但程序结束,所有资源将会返还给操作系统。
(4)不应该将finalize()作为通用的清理方法,因为无法确切地保证垃圾回收器何时调用该方法,也无法保证调用不同对象的方法的顺序。即使一个对象包含另一个对象的引用,或者在释放一个对象很久以前就释放了另一个对象,也可能会以任意的顺序调用这两个对象的Finalize方法。
(5)用途:
无论对象是如何创建的,垃圾回收都会负责释放对象占据的所有内存,所以finalize()应用的场景是通过某种创建对象方式以外的方式为对象分配了存储空间(java中一切皆为对象,这种情况主要是由于在分配内存时采用了“本地方法”,本地方法是一种在java中调用非java代码的方式。本地方法目前只支持C和C++,但可以调用其他任何语言。非java代码中也许会调用C中的malloc()函数系列来分配内存,而且除了free()函数,否则存储空间永远得不到释放,从而造成了内存泄漏。在finalize()中必须使用本地方法来调用free()方法)。
(6)关键点:
<1>对象可能不被垃圾回收;
<2>垃圾回收并不等于析构;
<3>垃圾回收只与内存有关;

3.实施清理:
要清理一个对象,用户必须在需要清理的时候调用执行清理动作的方法。C++中,不管是局部对象还是全局对象,都应在在其边界被销毁,调用delete,从而调用析构函数。java中,垃圾回收会处理不用的对象,但如果希望进行除释放存储空间之外的清理工作,就必须得明确调用某个恰当的java方法,此时该方法就类似于析构函数。

4.终结条件:
finalize()方法并不依赖于每次对于finalize()方法进行调用,finalize()可以发现程序中比较隐晦的缺陷,比如对象中存在没有被适当清理的部分。finalize()中可以用来设置 终结条件System.gc()方法用于强制进行终结动作,此时就可以通过验证finalize()中的终结条件来判断程序是否存在缺陷。

5.垃圾回收器:
(1)垃圾回收器对于提高对象在堆中的创建速度,有明显的效果;
(2)在某些java虚拟机中,堆的内存分配实现方式类似于传送带,当分配一个新的对象时,传送带向前移动一格,这就类似于将“堆指针”移动到尚未分配的区域。如果完全的类似于“传送带”,那势必会导致频繁的使用内存页面调度,需要比实际更多的内存,这将大大影响性能,最终内存资源将被耗尽。而垃圾回收机制的存在,将会解决这个问题,它将一边回收不用的空间,一边将堆中的对象紧凑排列,使“堆指针”更靠近传送带初始处。通过垃圾回收器对对象重新排列,实现了无限空间可供分配的堆模型;
(3)引用计数:
<1>一种简单但速度很慢的垃圾回收技术;
<2>每个对象都有一个引用计数器,当有引用连接至对象时,计数器加1,当引用离开作用域或被置为null时,计数器减1,某个对象的引用计数器为0时,则释放该对象的存储空间;
<3>当存在对象之间的循环引用时,就会出现缺陷,本该被回收的对象,但计数器不为0,再让垃圾回收器来定位的话就会产生大量的工作量;
<4>所以引用计数常用来说明垃圾回收的工作方式,但并没有应用在实际中;
(4)其余更快模式的垃圾回收器:
这种模式的垃圾回收器并不基于引用计数,对任何还在使用或者称为“活”的对象,会追溯到其存活在堆栈或静态存储区之中的引用。该引用链条可能会穿过多个对象层次。此时,从堆栈和静态存储区开始,遍历所有的引用,就能找到所有还在使用的对象。对于每个引用,必须追踪到其引用的对象,然后再寻找该对象包含的所有引用,重复下去,最终由堆栈和静态存储区的引用所形成的网络将全部被访问。这种方法所访问的对象全都是还在使用或是“活”的,其余未被访问的都将会被自动回收。
(5)java虚拟机的垃圾回收技术:
基于上面的思想,java虚拟机将采用一种 自适应的垃圾回收技术。对于如何处理找到的对象,取决于不同的java虚拟机,有两种方法, 停止-复制标记-清扫。这两种方法都是必须在程序暂停的情况下才能使用。
<1>停止-复制
原理:
该方法原理比较简单,首先会暂停程序的运行,然后将所有存活的对象从当前堆复制到另一个堆,没有被复制的都是垃圾,被复制到新堆的对象保持紧凑排列,然后即可按照(2)方法简单的进行新空间的分配。但是当对象从一处搬运到另一处时,所有指向它的引用都得修改,位于堆栈或者静态存储区的引用可以直接被修改,但是其他指向这些对象的引用只能在遍历的过程中进行修改。
缺点:
这种“复制型回收器”效率会降低,主要有两个原因。首先,是空间的问题,必须存在两个堆,要想复制,就得在两个堆之间来回操作,这样工作量就比以前多一倍,某些java虚拟机较好的处理方式是在堆中分配几块较大的内存,复制动作限制在这些内存块中。其次,在于复制,有时候程序稳定之后,可能产生少量的垃圾,甚至没有垃圾,但是对于这种回收器还是会将所有内存复制到另一处,这时就比较浪费了,处理方式是java虚拟机会进行检查,如果没有新垃圾,则转换成另一种工作模式,标记-清扫。
<2>标记-清扫
原理:
同停止-复制,从堆栈和静态存储区出发,遍历所有的引用,找出所有存活的对象,每当找到一个存活对象,就给对象设置一个标记,这个过程中不会回收任何对象。当全部标记完成之后,才会开始回收工作,未被标记的对象将被清理掉,不会产生复制动作。
缺点:
这种方法进行回收清理工作之后,剩下的堆空间是不连续的,如果要使堆空间连续,垃圾回收器就得重新排列堆空间。这种方式速度较慢,因为牵扯到标记之后才开始清理,但是如果垃圾较少或者没有垃圾,这种方法的速度就比较可观了。
(6)总结:
这里所讨论的java虚拟机中,内存分配都以较大的“块”为单位,当对象较大时,则会占用独立的块。停止-复制方法将会大量的使用内存复制行为,当有了块的概念之后,垃圾回收器只需要往废弃的块里拷贝对象即可。每个块都用相应的 代数来记录它是否废弃,是否存活。如果块在某处被引用,其代数将增加,并且垃圾回收器将对上次回收动作之后新分配的块进行整理。这对处理大量短命的临时对象很有帮助。垃圾回收器会定期进行完整的清理工作,大型对象仍然不会被复制,但是该对象的块的代数会增加,对于小型对象的那些块则被复制并整理。java虚拟机会对整个过程进行监视,如果所有对象稳定存在,垃圾回收器的效率较低,则会切换到标记-清扫模式;同样,虚拟机会跟踪标记-清扫模式,当堆空间出现过多的碎片,就会切换到停止-复制模式。这就是java自适应的,分代的,停止-复制的,标记-清扫的垃圾回收器。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值