垃圾回收器

来源:互联网 发布:网络3无internet访问 编辑:程序博客网 时间:2024/05/19 12:39

简介

Java的垃圾回收器是怎么进行回收的呢?总所周知,Java中所有的对象的实例都是在堆中分配内存,每一个方法的创建执行都会创建一个栈帧,这就是栈。把所有的实例都存放在对中,这个代价无疑是十分高昂的。

Java的堆为对象分配对象时,会有一个指针,这个指针指向空闲的内存区域,只是简单的移动到未分配的区域为实例分配地址。在不断地分配内存过程中,必然也要有一个管理内存区域的地方,这就是Java的垃圾回收器。一面为对象分配内存,紧凑排列,一面清理已经过期的对象,这样堆指针就更加容易的移动到空闲的区域。也避免了页面错误,通过垃圾回收器堆对象重新排列,实现了一种高速的,无限空间可供分配的堆模型。

垃圾回收器机制

对于其他系统中的垃圾回收机制,我可能并不了解,但是这里对JVM的垃圾回收机制,做一下深入了解。

计数机制

引用计数机制是一种简单但是速度很慢的模式,每一个对象都有一个计数器,当有引用连接时,计数器就+1,当引用域离开作用于或者被设置为null时,计数器-1,虽然管理引用计数器开销不大,但是在整个程序的生命周期都一直存在。垃圾回收器会对堆中所有的对象进行遍历所有的计数器,计数器为0时,就释放这个对象。但是这个方法有一个缺陷,就是对象之间如果循环引用,会存在对象应该被回收,但是计数器不为0。定位这样过的交互引用所需的工作量及其大,所有这个机制并未在任何系统中实现。

停止-复制(stop-and-copy)

上述计数机制,并不可靠,所有又引进一种新机制。他们依据的思想:对任何”活”的对象,一定可以找到存活在堆栈或静态存储区的引用,这个引用链条可以穿过数个对象层次,由此,从栈何静态存储快出发,遍历所有的引用,追踪它所应用的对象,然后是此对象包含的所有引用,如此反复进行,直到“”根源于栈和静态存储区的引用“全部被访问为止,你所有访问的对象都是活的,那么交互引用的对象组问题就解决了。

在这种情况下,一种自适应的垃圾回收机制就产生了,先暂停程序的运行,在把存活的对象复制到另外一个处,没有被复制的全部是垃圾,就回收掉。对象被复制到新的堆中,重新紧凑排列。
这种复制的垃圾回收也存在一个问题,始终就是在两个堆中反复复制复制,来回倒腾,因此实际维护中需要多一倍的时间。因此对此问题处理的方式是,按需求充堆中分配几块较大的内存,复制动作发生在这些大块内存之间就行。

标记-清扫(mark-and-sweep)

复制式的回收,所诞生的问题是。当我们的程序稳定后,只会产生相对少量的垃圾,这个时候用复制式的回收显然是得不偿失,效率较低,浪费性能。为了解决这种浪费现象,一些Java虚拟机会进行检查,要是没有新垃圾产生,就会自动切换到另外一种模式(标记-清扫)模式。

标记-清扫的思路是从栈和静态存储区出发,遍历所有的引用,找到所有的存活对象,为每一个存活的对象加上一个标记。当所有的标记动作完成过后,清理动作才开始,凡是没有被标记的就被清理。清理完成后,剩下的堆排列是不连续的,还需要虚拟机进行整理。

停止复制的问题

对于 停止-复制的模式,内存以较大的块为单位,而在回收之前,务必要进行较大的内存复制活动。对于某些较大的对象可能占用整个块,有了块后,垃圾回收期可以往废弃的堆中分配对象,每个块有相应的代数,如果快被引用,代数+1,垃圾回收器会对上次分配后的块进行整理。这显然对大量的临时对象很有用。随后,垃圾回收器会定期进行完整的清理动作—大型对象不会被复制(代数+1),小型对象会被复制到新的块中。Java虚拟机会对整个对象进行监控,如果产生了大量的碎片垃圾,就切换到停止复制模式,如果程序比较稳定,没有太多的垃圾产生,就切换到标记清扫模式。

这就是所谓的“自适应的,分代的,停止-复制,标记-清扫”式垃圾回收器。

析构函数

析构函数是C++中一种在对象呗销毁时可以被自动调用的函数。但是在Java中并没有这种概念,其原因是我们我们习惯了把销毁对象的工作交给了垃圾回收器。这样的好处是我们不用去显式去编写一个方法来清理。