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

关于垃圾回收

2015-12-28 21:00 369 查看
一、一些基础知识和finalize()方法
在C++中,对象一定会被销毁(前提是程序没有缺陷,程序员总能做到对资源的释放)或者说理应被销毁,而Java里的对象并非总是会被垃圾回收。
书上总结了3点:
1. 对象可能不被垃圾回收
2. 垃圾回收不等于“析构”
3. 垃圾回收只与内存有关
也就是说,垃圾回收的唯一原因就是为了回收程序不再使用的内存。

关于finaize()方法:
一旦垃圾回收器(GC)准备释放对象占用的存储空间时,首先调用其finalize()方法,并且在下一次垃圾回收动作时,才会真正回收对象占用的内存。所以如果打算用finalize(),就能在垃圾回收时刻做一些重要的清理工作。(下面引用《疯狂Java讲义》)垃圾回收机制只负责回收堆内存中的对象,不会回收任何物理资源(例如数据库连接、网络IO等资源),所以什么时候需要重写finalize()呢?总结一下:
1. 使用native方法的情况,调用C/C++代码时,可能会用malloc()之类的函数分配内存,所以除非显式调用free()函数,否则存储空间就得不到释放。因此,需要在finalize()中用本地方法进行调用。
2. 对象“终结条件”(这个词由Bill Venners发明)验证。即当对某个对象不感兴趣——也就是它可以被清理了, 这个对象应该处于某种状态,使它占用的内存可以被安全地释放。eg.要是对象代表了一个打开的文件,在对象被回收前程序员应该关闭这个文件。只要对象中存在没有被适当清理的部分,程序就存在很隐晦的缺陷。finalize()可以用于最终发现这种情况——尽管它不总是被调用。如果某次finalize()的动作使得缺陷被发现,那么就可据此找出问题的所在。例子:
class Book {
boolean checkedOut=true;

Book(boolean checkOut){
checkedOut=checkOut;
}

void checkIn(){
checkedOut=false;
}

@Override
protected void finalize() throws Throwable {
if(checkedOut){
System.out.println("Error: checked out");
}
//Normally, you'll also do this:
//      super.finalize();   //Call the base-class version
}
}

public class TerminationCondition{
public static void main(String args[]){
Book novel=new Book(true);
//Proper clean up:
novel.checkIn();
//Drop the reference, forget to clean up:
new Book(true);
//Force garbage collection & finalization:
System.gc();
}
}


Output:

Error: checked out

本例的终结条件是:所有的Book对象在被GC回收前都应该被签入(check in)。但在main()方法中,由于程序员的失误,有一本书未被签入。要是没有finalize()来验证终结条件,将很难发现这样的缺陷。

二、GC如何工作
在堆上创建对象的速度:
Java从堆上分配空间的速度,可以和其他语言从栈上分配空间的速度相媲美。打个比方,可以把C++里的堆想象成一个院子,里面每个对象都负责管理自己的地盘,经过一段时间,对象可能被销毁,但是地盘必须得到重用。某些JVM中,堆的实现像一个传送带,每分配一个对象,传送带就往前移动一格,其效率比得上C++在栈中分配空间的效率。
GC工作时,一面回收空间,一面使堆中的对象紧凑排列(对内存碎片进行整理),从而实现一种高速的、有无限空间可供分配的堆模型。

如何检测存活的对象:
方法1. 引用计数。每个对象包含一个引用计数器,当有引用指向对象时计数器加一,当引用离开作用域或置为null时减一。这种方法需要GC在引用计数器列表上不断遍历,监测到某个对象的计数器为0时立刻回收该对象。而且缺点是不能检测循环引用(有些对象可能会循环引用,尽管没有其他引用指向这些对象,理应回收,但引用计数器却一直不为0)。这种办法似乎未被应用于任何JVM的实现中
方法2. 对任何“活”的对象,一定能最终追溯到其存活在静态存储区上的引用。因此,从栈或静态存储区开始,遍历所有引用,就能找到所有的“活”的对象。这种方法中,循环引用的对象组根本不会被发现,所以解决了上面的问题。
针对不同的JVM,检测“活”的对象的方式取决于其自身的实现。

GC的工作方式:
方式1. 停止-复制(stop-and-copy):不属于后台回收方式。先暂停程序的运行,然后将当前所有存活的对象从当前堆复制到新堆(或在堆中开辟几片空间,从一个空间复制到另一空间),当对象被复制到新堆后,是一个挨一个的,所以不需要再进行对象碎片的整理。然后,修正所有对象的引用(地址)。存在于静态存储区和栈的引用可直接修正,存在于堆中对象中的引用需要在沿着引用链遍历的过程中再被修正。
方式2. 标记-清扫(mark-and-sweep):Sun的早期JVM使用这种技术。从栈和静态存储区出发,遍历所有引用,进而找出所有“活”的对象,每当找到一个“活”的对象,为其设一个标记,这个过程不会回收任何对象,只有当全部标记完成才会开始清理。这种方式相当慢,但如果程序运行稳定后,产生较少的垃圾时,这种方式速度就会很快了。
实际上JVM会对以上方式进行结合,如果对象基本上稳定,就使用“标记-清扫”方式;如果堆空间中出现较多碎片,此时“标记-清扫”效率会很低,就切换到“停止-复制”方式。

a22e
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java 垃圾回收 GC