Work Better Than Yesterday!

zhangge's stupid and messy life


Home| Life| Technique Concentrate On One Thing.

Java系列之GC垃圾回收机制

02 Jun 2013

1 什么是GC

学过操作系统,我们知道如果要使用内存就向系统申请一块指定大小的内存,然后系统会返回一个起始地址给我们,于是在我们使用完毕以后需要再告诉系统这个内存使用完毕了,是闲置的了,别人就可以再使用了。所以我们在学习C语言的时候对应就有malloc和free函数,而C++里面的类还有析构函数。如果程序员很好,写的代码没有缺陷,总在合理的地方和时候释放对象,那么对象是一定会被销毁的,而在JAVA里的对象却并非总被垃圾回收。就是说,对象可能不被垃圾回收,垃圾回收不等于“析构”,垃圾回收只与内存有关。

在JAVA里面,因为有虚拟机的存在,一切都变得那么的轻松,我们不需要懂得内存就能够写代码了,一句new就能把内存申请好了,最后再把引用设置为null,对象就会被回收了,我们完全不用关系这些管理内存的事情了。虚拟机帮分配了内存给你使用,而且还会动态去扩大对象的内存,当对象以后没用了,GC会知道,然后帮你释放这个对象的内存。但是,再优秀的自动化管理机制还是有缺陷的,如果我们不懂得GC的机制,依然会导致对象回收不了,程序的性能低下,甚至出现内存溢出,内存泄露等问题,还是得去排查问题。

2 什么是引用

引用是一个被管理和使用的地址,是对地址的封装,它既是名词也是动词,虽然本质上它就是一个地址,但是在意义上还是有区别的。以前JDK对引用的定义是,如果reference类型的数据中存储的数值代表的是另一块内存的起始地址,就称这块内存代表着一个引用。可见,引用本质就是内存地址,即指针,而且还封装成为了reference类型,这个和window那边说的句柄一个意思,只不过封装了更多的东西而已。地址是一个静态的名词,而引用除了静态地表示一块内存地址外,它还是可以动态地使用起来的,用于指向某一个对象,并且还可以统计对象被引用的次数。举个例子,一般来说,论文的最后一节都是展示引用了那些别的论文,这些就是引用了,而我们可以通过这些引用找到相应的url(即地址)去链接到别的论文,而别的论文是可以统计知道自己被引用了多少次的。如果你单纯使用地址的话,就没有了这个引用次数的意义了。

因此引用的最大作用就是可以让对象知道自己有没有被引用,即被别人使用自己,并且知道被引用的次数。有了引用和次数,垃圾回收就可以实现了,基本原理就是,假如对象没有了引用,即没人使用了,那么就可以回收这个对象了。但是,有时候有些对象我们不太想马上就回收,当空间还足的时候就让他留在内存,相当于缓存,如果内存不足了,就要回收了。于是就出现了下面四种不同的引用类型,反正引用也是一种类型而已。

2.1 强引用StrongReference

平时我们使用的都是强引用。只要强引用还在,对象就不会被回收。

2.2 软引用SoftReference

如果一个对象具有软引用,内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。即软引用用来描述还有用但非必需的对象。当进行回收的时候会把软引用的对象列为第二次回收的范围,如果第二次回收还内存不足就抛出异常。软引用可用来实现内存敏感的高速缓存。使用软引用能防止内存泄露,增强程序的健壮性。SoftReference的特点是它的一个实例保存对一个Java对象的软引用,该软引用的存在不妨碍垃圾收集线程对该Java对象的回收。也就是说,一旦SoftReference保存了对一个Java对象的软引用后,在垃圾线程对这个Java对象回收前,SoftReference类所提供的get()方法返回Java对象的强引用。

2.3 弱引用WeakReference

弱引用的强度比软引用更弱一些,它拥有比较短暂的生命周期,在垃圾回收器扫描它所管辖的内存区域过程中,一旦发现了只具有弱引用的对象,就会回收它的内存,就是说它只能活到下次回收之前;不过一般情况下,垃圾回收器的线程优先级很低,也就不会很快发现那些只有弱引用的对象。

2.4 虚引用,幽灵引用,幻影引用PhantomReference

三个名字,最弱的引用关系,完全不能保证对象的存活时间,也无法通过虚引用来获取一个对象的实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被回收时收到一个系统通知。但是目前我还不知道怎么使用。

3 回收算法

不同的虚拟机对于回收算法都有不同的实现细节和使用策略,但是算法思想基本上都是固定的了。

3.1 对象回收的依据

要回收内存,就是释放对象,那么我们得知道哪些对象可以回收了。

3.1.1 引用计数法

这是一种旧的回收机制,并且基本不用的了,给每个对象一个引用计数器,当计数器为0时就回收对象,但是有缺陷,就是如果AB两个对象互相有对方引用时就产生循环,无法回收。在ObjectC中,解决这个循环引用的回收问题采用的是弱引用方法。但是java的gc却使用了下面新的算法。

3.1.2 可达性分析法

为解决循环引用的问题,必须设置一个根节点,从根节点通过引用传递能够到达的对象,那么就是活的,不能到达的就是游离状态的对象,即使它们循环引用,也没关系,依然会被回收的。这个根节点就是GC Root,它是一个对象的集合,这个集合的对象包括:

1.虚拟机栈(栈帧中的本地变量表)中引用的对象
2.方法区中类静态属性引用的对象
3.方法区中常量引用的对象
4.本地方法栈中JNI,即native方法,引用的对象

3.2 回收的机制

上面的可达性分析只是说了哪些对象可以回收,但是没有涉及回收以后内存的处理策略是怎么样。

3.2.1 标记-清除算法

就是打标签的方法,通过可达性分析法遍历所有的对象,如果对象还有被引用,就是活的,打一个标签。最后将没有标记的死对象清理,这样的结果会产生很多内存碎片,需要整理成连续空间。这个算法一般不会使用。

3.2.2 标记-整理算法

针对标记-清除算法产生的内存碎片,于是产生了一个升级版的算法,就是不清理死对象,而是移动活对象到一端,再对活对象边界以外进行清理。这个算法适用于存活率比较高的情况。

3.2.3 停止-复制算法

标记-清除的效率不高,于是有了这个算法(怎么解决效率问题的?),将堆分成两个区域,先暂停程序运行(不是后台运行的,所以慢啊),然后将标记活的对象复制到另一个堆,并修正引用;再对原来的堆进行全盘清理。这两个堆的大小比例不一样会影响回收效率,主要是分析对象的存活率问题。刚开始设计的是两个堆的比例是50%:50%,这样明显浪费了一般的空间,于是IBM公司的算法是分成3个区,一个大的eden区,两个小的survivor区,hotspot虚拟机中的eden和survivor的比例是8:1。每次都使用eden和其中一个survivor区,回收的时候把活的对象复制到另一个survivor区,然后清理另外两个区。明显这个算法适合存活率比较低的情景。并且一旦survivor空间不足还需要分配。

3.2.4 分代的自适应算法

为了提高停止-复制的效率,会将内存划分更多的块,一个块可以存放一个对象,也可以多个对象,每个块有一个代数记录是否还存活,如果块被引用(及块里面的对象),则代数(generation count)增加。清理时大对象不会被复制。小型对象被复制整理到其他块。严格来讲,这不是新算法。

这个算法一般把内存分为新生代区和老年代区这两种区(上面说了,是多个区,不止两个的),根据不同年代的特点使用最适合的算法。在新生代中存活率低,就使用停止复制算法,而在老年代中,存活率高,而且没有额外空间进行分配担保,就必须使用标记-清除或者标记-整理算法。

4 关于finalize方法

finalize方法不是c的free方法,虽然本质上是调用free,但是不要直接调用,他的结果无法预料,是危险的。无论是回收还是终结,都不保证一定会发生。如果jvm并未面临内存耗尽的情形,它是不会浪费时间去执行垃圾回收以恢复内存的。

gc回收一个对象的时候会先判断这个对象有没有覆盖重写finalize方法,如果有就回放到一个低优先级的队列中去执行,暂时不回收,并且执行不能确定,也不会等待finalize方法执行完成,因为万一阻塞就会卡死了。并且只会执行一次,下次回收这个对象的时候判断已经执行过一次了就会被打上死对象标签了。虽然我们知道finalize会被执行一次,但是如上面所说,太多不确定因素了,不要想着在这里拯救对象,例如给它一个引用之类的,因为我们实在不能保证拯救会成功。

5 回收方法区

上面所有讨论的回收算法都是在堆进行的,因为那里的回收效率最高,而且容易,但是方法区也是需要回收的地方。假如一些常量已经不用了,那么常量池当中就要去掉这些常量,另外类、方法、字段的符号引用也是。最重要的是如何把一个class回收,还有Class对象。虚拟机规范中定了下面三个条件:

1.该类所有的实例都已经被回收,也就是java堆中不存在该类的任何实例。
2.加载该类的ClassLoader已经被回收(但是系统的ClassLoader不会被回收?)
3.该类对应的Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

6 回收其他资源

使用垃圾回收器的唯一原因是为了回收程序不再使用的内存。但是,如果通过一些特殊的方式创建为对象分配了内存,如在屏幕绘制图像,则需要手动擦除这个图像。打开文件也需要手动关闭,这些系统资源就是java的内存泄漏原因之一,还有一个就是引用所占的内存,如果不清空回收,也会泄漏的。


Sunday don't come easily! Subscribe to RSS Feed