当前位置:首页 > 开发 > 编程语言 > 编程 > 正文

浅析GC工作原理

发表于: 2011-05-20   作者:爪哇岛岛主   来源:转载   浏览:
摘要:     Java 的内存管理实际上就是对象的管理,其中包括对象的分配和释放。 对于程序员来说,分配对象使用 new 关键字;释放对象时,只要将对象所有 引用赋值为 null,让程序不能够再访问到这个对象,我们称该对象为"不可达 的".GC 将负责回收所有"不可达"对象的内存空间。     
    Java 的内存管理实际上就是对象的管理,其中包括对象的分配和释放。 对于程序员来说,分配对象使用 new 关键字;释放对象时,只要将对象所有 引用赋值为 null,让程序不能够再访问到这个对象,我们称该对象为"不可达 的".GC 将负责回收所有"不可达"对象的内存空间。
     对于 GC 来说,当程序员创建对象时,GC 就开始监控这个对象的地址、大小 以及使用情况。通常,GC 采用有向图的方式记录和管理堆(heap)【备注】中的所有对象。通过这种方式确定哪些对象是"可达的",哪些对象 是"不可达的".当 GC 确定一些对象为"不可达"时,GC 就有责任回收这些内存空间。但是,为了保证 GC 能够在不同平台实现的问题,Java 规范对 GC 的很多行为都没有进行严格的规定。例如,对于采用什么类型的回收算法、什么时候 进行回收等重要问题都没有明确的规定。因此,不同的 JVM 的实现者往往有不同的实现算法。这也给 Java 程序员的开发带来行多不确定性。
     本文研究了几个与 GC 工作相关的问题,努力减少这种不确定性给 Java 程序带来的负面影响。
     增量式 GC( Incremental GC ), GC 在 JVM 中通常是由一个或一组进程来实现的,它本身也和用户程序一样 占用 heap 空间,运行时也占用 CPU,当 GC 进程运行时,应用程序停止运行。因此,当 GC 运行时间较长时,用户能够感到 Java 程序的停顿,另外一方面,如果 GC 运行时间太短,则可能对象回收率太低,这意味着还有很多应该回收的对象 没有被回收,仍然占用大量内存。因此,在设计 GC 的时候,就必须在停顿时间 和回收率之间进行权衡。一个好的 GC 实现允许用户定义自己所需要的设置,例 如有些内存有限有设备,对内存的使用量非常敏感,希望 GC 能够准确的回收内存,它并不在意程序速度的放慢。另外一些实时网络游戏,就不能够允许程序有长时间的中断。增量式 GC 就是通过一定的回收算法,把一个长时间的中断,划分为很多个小的中断,通过这种方式减少 GC 对用户程序的影响。虽然,增量式 GC 在整体性能上可能不如普通 GC 的效率高,但是它能够减少程序的最长停顿时 间。 Sun JDK 提供的 HotSpot JVM 就能支持增量式 GC.HotSpot JVM 缺省 GC 方式为不使用增量 GC,为了启动增量 GC,我们必须在运行 Java 程序时增加-Xincgc 的参数。HotSpot JVM 增量式 GC 的实现是采用 Train GC 算法。它的基本想法就是,将堆中的所有对象按照创建和使用情况进行分组(分层),将使用频繁高和 具有相关性的对象放在一队中,随着程序的运行,不断对组进行调整。当 GC 运 行时,它总是先回收最老的(最近很少访问的)的对象,如果整组都为可回收对 象,GC 将整组回收。这样,每次 GC 运行只回收一定比例的不可达对象,保证程 序的顺畅运行。
     函数 finalize 是位于 Object 类的一个方法, 该方法的访问修饰符为 protected, 由于所有类为 Object 的子类,因此用户类很容易访问到这个方法。由于, finalize 函数没有自动实现链式调用,我们必须手动的实现,因此 finalize 函 数的最后一个语句通常是 super.finalize()。通过这种方式,我们可以实现 从下到上实现 finalize 的调用,即先释放自己的资源,然后再释放父类的资源。 根据 Java 语言规范,JVM 保证调用 finalize 函数之前,这个对象是不可达 的,但是 JVM 不保证这个函数一定会被调用。另外,规范还保证 finalize 函数 最多运行一次。 很多 Java 初学者会认为这个方法类似与 C++中的析构函数,将很多对象、 资源的释放都放在这一函数里面。其实,这不是一种很好的方式。原因有三,其 一,GC 为了能够支持 finalize 函数,要对覆盖这个函数的对象作很多附加的工 作。其二,在 finalize 运行完成之后,该对象可能变成可达的,GC 还要再检查 一次该对象是否是可达的。因此,使用 finalize 会降低 GC 的运行性能。其三, 由于 GC 调用 finalize 的时间是不确定的, 因此通过这种方式释放资源也是不确 定的。 通常,finalize 用于一些不容易控制、并且非常重要资源的释放,例如一 些 I/O 的操作,数据的连接。这些资源的释放对整个应用程序是非常关键的。在这种情况下,程序员应该以通过程序本身管理(包括释放)这些资源为主,以 finalize 函数释放资源方式为辅,形成一种双保险的管理机制,而不应该仅仅依靠 finalize 来释放资源。 下面给出一个例子说明,finalize 函数被调用以后,仍然可能是可达的, 同时也可说明一个对象的 finalize 只可能运行一次。
class MyObject{ 
Test main; //记录 Test 对象,在 finalize 中时用于恢复可达性 
public MyObject(Test t) { 
main=t; //保存 Test 对象 
} 
protected void finalize() { 
main.ref=this;// 恢复本对象,让本对象可达 
System.out.println(\"This is finalize\");//用于测试 finalize 只运 行一次 
} 
} 
class Test { 
MyObject ref; 
public static void main(String[] args) { 
Test test=new Test(); 
test.ref=new MyObject(test); 
test.ref=null; //MyObject 对象为不可达对象,finalize 将被调用 
System.gc(); 
if (test.ref!=null)
System.out.println(\"My Object 还活着\"); 
} 
} 
运行结果:
This is finalize 
MyObject 还活着 

此例子中,需要注意的是虽然 MyObject 对象在 finalize 中变成可达对象, 但是下次回收时候,finalize 却不再被调用,因为 finalize 函数最多只调用一 次。 程序如何与 GC 进行交互 Java2 增强了内存管理功能, 增加了一个 java.lang.ref 包,其中定义了 三 种 引 用 类 。 这 三 种 引 用 类 分 别 为 SoftReference 、 WeakReference 和 PhantomReference.通过使用这些引用类,程序员可以在一定程度与 GC 进行交 互,以便改善 GC 的工作效率。这些引用类的引用强度介于可达对象和不可达对 象之间。 创建一个引用对象也非常容易,例如如果你需要创建一个 Soft Reference 对象,那么首先创建一个对象,并采用普通引用方式(可达对象);然后再创建 一个 SoftReference 引用该对象;最后将普通引用设置为 null.通过这种方式, 这个对象就只有一个 Soft Reference 引用。同时,我们称这个对象为 Soft Reference 对象。 Soft Reference 的主要特点是据有较强的引用功能。只有当内存不够的时 候,才进行回收这类内存,因此在内存足够的时候,它们通常不被回收。另外, 这些引用对象还能保证在 Java 抛出 OutOfMemory 异常之前,被设置为 null.它 可以用于实现一些常用图片的缓存,实现 Cache 的功能,保证最大限度的使用内 存而不引起 OutOfMemory.以下给出这种引用类型的使用伪代码;
//申请一个图像对象 
Image image=new Image();//创建 Image 对象 
… //
使用 image … //
使用完了 image,将它设置为 soft 引用类型,并且释放强引用; 
SoftReference sr=new SoftReference(image); 
image=null; … //下次使用时 
if (sr!=null) 
image=sr.get(); 
else{ //由于 GC 由于低内存,已释放 image,因此需要重新装载; image=new Image(); sr=new SoftReference(image); 
}


Weak 引用对象与 Soft 引用对象的最大不同就在于:GC 在进行回收时,需要 通过算法检查是否回收 Soft 引用对象,而对于 Weak 引用对象,GC 总是进行回 收。Weak 引用对象更容易、更快被 GC 回收。虽然,GC 在运行时一定回收 Weak 对象,但是复杂关系的 Weak 对象群常常需要好几次 GC 的运行才能完成。Weak 引用对象常常用于 Map 结构中,引用数据量较大的对象,一旦该对象的强引用为 null 时,GC 能够快速地回收该对象空间。 Phantom 引用的用途较少,主要用于辅助 finalize 函数的使用。Phantom 对象指一些对象,它们执行完了 finalize 函数,并为不可达对象,但是它们还 没有被 GC 回收。这种对象可以辅助 finalize 进行一些后期的回收工作,我们通 过覆盖 Reference 的 clear()方法,增强资源回收机制的灵活性。
一些 Java 编码的建议,根据 GC 的工作原理, 我们可以通过一些技巧和方式, GC 运行更加有效率, 让 更加符合应用程序的要求。以下就是一些程序设计的几点建议。
1.最基本的建议就是尽早释放无用对象的引用。 大多数程序员在使用临时变 量的时候,都是让引用变量在退出活动域(scope)后,自动设置为 null.我们 在使用这种方式时候,必须特别注意一些复杂的对象图,例如数组,队列,树, 图等,这些对象之间有相互引用关系较为复杂。对于这类对象,GC 回收它们一 般效率较低。如果程序允许,尽早将不用的引用对象赋为 null.这样可以加速 GC 的工作。
2.尽量少用 finalize 函数。finalize 函数是 Java 提供给程序员一个释放 对象或资源的机会。但是,它会加大 GC 的工作量,因此尽量少采用 finalize 方式回收资源。
3.如果需要使用经常使用的图片,可以使用 soft 应用类型。它可以尽可能 将图片保存在内存中,供程序调用,而不引起 OutOfMemory.
4.注意集合数据类型,包括数组,树,图,链表等数据结构,这些数据结构 对 GC 来说,回收更为复杂。另外,注意一些全局的变量,以及一些静态变量。 这些变量往往容易引起悬挂对象(dangling reference),造成内存浪费。
5.当程序有一定的等待时间,程序员可以手动执行 System.gc(),通知 GC 运行, 但是 Java 语言规范并不保证 GC 一定会执行。 使用增量式 GC 可以缩短 Java 程序的暂停时间。

【备注】堆(heap):当程序运行时,虚拟机会把所有该程序在运行时创建的对象都放到堆中

【附】JAVA虚拟机运行过程。

浅析GC工作原理

  • 0

    开心

    开心

  • 0

    板砖

    板砖

  • 0

    感动

    感动

  • 0

    有用

    有用

  • 0

    疑问

    疑问

  • 0

    难过

    难过

  • 0

    无聊

    无聊

  • 0

    震惊

    震惊

版权所有 IT知识库 CopyRight © 2009-2015 IT知识库 IT610.com , All Rights Reserved. 京ICP备09083238号