`
爪哇岛岛主
  • 浏览: 38755 次
  • 性别: Icon_minigender_1
  • 来自: 杭州(也就是天堂)
社区版块
存档分类
最新评论

浅析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虚拟机运行过程。
  • 大小: 5.4 KB
分享到:
评论

相关推荐

    .NET垃圾回收器(GC)原理浅析

    本着“通俗易懂”的原则,本文将解释CLR中垃圾回收器的工作原理。 基础知识 托管堆(Managed Heap) 先来看MSDN的解释:初始化新进程时,运行时会为进程保留一个连续的地址空间区域。这个保留的地址空间被称为托管堆。...

    浅析C#编程中的内存管理

    在C#编程中,内存管理是一项关键的概念,虽然程序员通常不需要直接进行内存分配和释放,但理解其工作原理对于编写高效、无泄漏的代码至关重要。本文将深入探讨C#中的内存管理,特别是值类型和引用类型如何在内存中...

    浅析C# 内存管理

    ### 浅析C# 内存管理 #### 内存管理概述 ...通过掌握值类型与引用类型的差异、垃圾回收的工作原理以及如何正确管理非托管资源,开发者可以更有效地利用.NET提供的自动化内存管理特性,同时确保程序的稳定性和效率。

    javascript内存浅析

    这篇博文“javascript内存浅析”将带领我们探索JavaScript的内存模型及其工作原理。 首先,我们要了解JavaScript的内存分为两个主要区域:栈内存和堆内存。栈内存主要用来存储基本类型(如number、string、boolean...

    浅析Java中的GC垃圾回收器的意义及与GC的交互

    Java中的GC(Garbage Collection)垃圾...总的来说,Java的GC机制极大地简化了内存管理,减少了因手动管理内存可能导致的错误,但同时也需要开发者理解其工作原理,以便在必要时优化内存使用,提高应用的性能和稳定性。

    浅析JVM

    ### 浅析JVM:垃圾回收与内存模型 #### 一、JAVA语言及JVM简介 **JAVA语言**是由Sun Microsystems公司(后被Oracle收购)的詹姆斯·高斯林(James Gosling)等人于1995年设计并发布的。JAVA语言是一种通用型、面向...

    安卓Android源码——防止内存溢出浅析.zip

    在Android源码中,GC的工作原理和策略是关键。Dalvik/ART虚拟机采用分代收集算法,将堆分为新生代、老年代等区域,根据对象的存活周期进行不同策略的回收。 防止Android应用出现内存溢出,有以下几点策略: 1. **...

    Android防止内存溢出浅析

    首先,我们需要理解Android内存管理的基本原理。Android应用程序基于Java语言,因此其内存机制与Java相似。Java的垃圾回收(GC)机制会自动回收不再使用的对象,但并不意味着开发者可以完全忽视内存管理。Android系统...

    浅析JAVA之垃圾回收机制.doc

    然而,虽然GC简化了编程,但了解其工作原理对于优化程序性能至关重要。 【finalize()方法】 `finalize()`方法是Java中每个对象都继承自`Object`类的一个特殊方法。尽管常被误认为是析构函数,但实际上它的主要作用...

    浅析JVM逃逸的原理及分析

    1. 栈上分配:一般情况下,不会逃逸的对象所占空间比较大,如果能使用栈上的空间,那么大量的对象将随方法的结束而销毁,减轻了 GC 压力。 2. 同步消除:如果你定义的类的方法上有同步锁,但在运行时,却只有一个...

    Android应用源码之防止内存溢出浅析-IT计算机-毕业设计.zip

    这个"Android应用源码之防止内存溢出浅析"的压缩包可能包含了示例代码,通过阅读和分析这些代码,你可以理解以上策略如何在实际项目中应用。例如,查看图片加载部分,是否使用了合适的解码选项;检查Activity的生命...

    浅析JVM垃圾回收的过程

    JVM垃圾回收是Java编程中不可或缺的一部分,理解其工作原理有助于优化应用程序性能,避免内存泄漏,并确保系统资源的有效利用。对于Java开发者来说,掌握这些知识是至关重要的,以便更好地理解和调试与内存管理相关...

    浅析python中的del用法

    当一个对象的引用计数变为0时,Python的垃圾回收(Garbage Collection, GC)机制会自动回收这个不再使用的对象所占用的内存。以下是一个简单的例子: ```python if __name__ == '__main__': a = 1 # 创建一个整数...

Global site tag (gtag.js) - Google Analytics