您的位置:首页 > 编程语言 > Java开发

《Thinking in Java》——垃圾回收机制

2017-05-05 21:26 267 查看
垃圾回收器只会释放那些经由new分配的内存

对象可能不被垃圾回收

垃圾回收并不等于“析构”

垃圾回收只与内存有关

垃圾回收机制

(一)几种垃圾回收技术

1.引用计数

引用计数是一种很简单但是很慢的垃圾回收技术,每个对象都含有一个引用计数器,当有引用连接到对象时,引用计数加1。当引用离开作用域或者被置null时,引用计数减1。虽然管理引用计数的开销不大,但是这项开销会在整个程序生命周期中持续发生。垃圾回收器会在含有全部对象的列表上遍历,当发现某一个对象的引用计数为0时,就释放它占用的空间。

但是这种方法有个缺陷,当对象之间存在循环引用的时候,可能出现“对象应该被回收,但是引用计数却不为零”的情况,如:

class A{
public B b;
}
class B{
public A a;
}
public class Main{
public static void main(String[] args){
A a = new A();
B b = new B();
a.b=b;
b.a=a;
}
}


在函数的结尾,a和b的计数均为2

先撤销a,然后a的计数为1,在等待b.a对a的引用的撤销,也就是在等待b的撤销

对于b来讲,也是同理

两个对象都在等待对方撤销,所有这两个资源均不能释放

“活”对象

如果从堆栈或者静态存储区开始,遍历所有的引用,就能找到所有“活”的对象。对于发现的每个引用,必须追踪它所引用的对象,然后是这个对象包含的所有引用,如此反复进行,直到“根源于堆栈和静态存储区的引用”所形成的网络全部被访问为止,所有被访问过的对象就是“活”的对象。

2.停止-复制

(1) 停止:先暂停程序的运行(所以它不属于后台回收模式);

(2) 复制:然后将所有存活的对象从当前堆复制到另一个堆,没有被复制的对象都是垃圾,当对象被复制到新堆时,它们是一个挨着一个的,所以新堆保持紧凑排列,就可以简单直接的分配新空间了;

(3) 修改引用:当一个对象从一处搬到另一处时,所有指向它的引用都需要修改。位于堆和静态存储区的引用可以直接修改,但可能还有其他指向这些对象的引用,它们在遍历的过程中才可以被找到。

[b]缺点:效率低[/b]

[b]原因:[/b]

(1)需要有两个堆,在这两个分离的堆之间互相倒腾,从而得维护比实际需要多一倍的空间。某些Java虚拟机对此问题的处理方式是,按需从堆中分配几块较大的内存,复制动作发生在这些大块内存之间。

(2) 复制。程序进入稳定状态时,只会产生少量的垃圾,甚至没有垃圾,但是回收器还会将所有内存从一处复制至另一处,这很浪费。

3.标记-清扫

(1)停止:暂停程序

(2) 标记:从堆栈和静态存储区出发,寻找“活”对象时并标记

(3)清扫:遍历全部对象后,清扫未被标记的对象

[b]缺点:速度慢[/b]

但是当只产生少量垃圾甚至不会产生垃圾时,它的速度就会很快

(二)Java虚拟机中“自适应”垃圾回收技术

内存分配以较大的“块”为单位,如果对象较大,它会占用单独的块。

有了块之后,垃圾回收器的回收的时候就会往废弃的块中拷贝对象了。每个块都有相应的代数来记录它是否还存活。通常,如果块在某处被引用,其代数会增加;垃圾回收器将对上次回收动作之后新分配的块进行整理。这对处理大量短命的临时对象很有帮助。(??????)垃圾回收器会定期进行完整的清理动作——大型对象仍然不会被复制(只是其代数会增加),内含小型对象的那些块则被复制并整理。

Java虚拟机会进行监视,如果所有对象都很稳定,垃圾回收器的效率降低的话,就切换到“标记-清扫”的方式;

同样,Java虚拟机会跟踪“标记-清扫”的效果,如果堆空间中出现很多碎片,就会切回“停止-复制”方式,这就是“自适应”技术。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: